2009-10-21 15 views
23

In un'applicazione Silverlight 3.0 sto tentando di creare un rettangolo in una tela e farlo estendere per tutta la larghezza della tela. Ho tentato di farlo legando alla proprietà ActualWidth di un contenitore genitore (sembra esempio sotto), tuttavia mentre non vedo alcun errore di associazione il valore non viene associato. Il rettangolo non è visibile poiché la sua larghezza è zero. Inoltre, ho provato a legare allo ActualWidth della tela che contiene il mio rettangolo, ma questo non ha fatto alcuna differenza.Collegamento a ActualWidth non funziona

Ho fatto find this bug logged on Microsoft Connect ma non c'erano soluzioni alternative elencate.

Qualcuno è stato in grado di risolvere questo problema o può indicare una soluzione?

Modifica: l'esempio del codice originale non era preciso rispetto a quello che sto cercando di ottenere, aggiornato per maggiore chiarezza.

<UserControl> 
    <Border BorderBrush="White" 
      BorderThickness="1" 
      CornerRadius="4" 
      HorizontalAlignment="Center"> 
     <Grid x:Name="GridContainer"> 
      <Rectangle Fill="Aqua" 
         Width="150" 
         Height="400" /> 
      <Canvas> 
       <Rectangle Width="{Binding Path=ActualWidth, ElementName=GridContainer}" 
          Height="30" 
          Fill="Red" /> 
      </Canvas> 

      <StackPanel> 
       <!-- other elements here --> 
      </StackPanel> 
     </Grid> 
    </Border> 
</UserControl> 

risposta

31

Che cosa stai provando a fare questo richiede di associare i dati alla proprietà ActualWidth? Questo è un problema noto con Silverlight e non esiste una soluzione semplice.

Una cosa che si potrebbe fare è impostare l'albero visivo in modo tale che non sia necessario impostare effettivamente la larghezza del rettangolo e lasciare che si allunghi alle dimensioni appropriate. Quindi nell'esempio sopra, se rimuovi la tela (o cambia la tela su un altro pannello) e lasci lo Rectangle di HorizontalAlignment impostato su Stretch, occuperà tutta la larghezza disponibile (effettivamente la larghezza della griglia).

Tuttavia, questo potrebbe non essere possibile nel tuo caso particolare e potrebbe essere davvero necessario impostare il databinding. È già stato stabilito che ciò non è possibile direttamente, ma con l'aiuto di un oggetto proxy, possiamo impostare l'associazione richiesta. Considerate questo codice:

public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 

    public FrameworkElement Element 
    { 
     get { return (FrameworkElement)GetValue(ElementProperty); } 
     set { SetValue(ElementProperty, value); } 
    } 

    public double ActualHeightValue 
    { 
     get{ return Element == null? 0: Element.ActualHeight; } 
    } 

    public double ActualWidthValue 
    { 
     get { return Element == null ? 0 : Element.ActualWidth; } 
    } 

    public static readonly DependencyProperty ElementProperty = 
     DependencyProperty.Register("Element", typeof(FrameworkElement), typeof(ActualSizePropertyProxy), 
            new PropertyMetadata(null,OnElementPropertyChanged)); 

    private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     ((ActualSizePropertyProxy)d).OnElementChanged(e); 
    } 

    private void OnElementChanged(DependencyPropertyChangedEventArgs e) 
    { 
     FrameworkElement oldElement = (FrameworkElement)e.OldValue; 
     FrameworkElement newElement = (FrameworkElement)e.NewValue; 

     newElement.SizeChanged += new SizeChangedEventHandler(Element_SizeChanged); 
     if (oldElement != null) 
     { 
      oldElement.SizeChanged -= new SizeChangedEventHandler(Element_SizeChanged); 
     } 
     NotifyPropChange(); 
    } 

    private void Element_SizeChanged(object sender, SizeChangedEventArgs e) 
    { 
     NotifyPropChange(); 
    } 

    private void NotifyPropChange() 
    { 
     if (PropertyChanged != null) 
     { 
      PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue")); 
      PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue")); 
     } 
    } 
} 

possiamo usare questo in XAML come segue:

<Grid x:Name="LayoutRoot"> 
    <Grid.Resources> 
     <c:ActualSizePropertyProxy Element="{Binding ElementName=LayoutRoot}" x:Name="proxy" /> 
    </Grid.Resources> 
    <TextBlock x:Name="tb1" Text="{Binding ActualWidthValue, ElementName=proxy}" /> 
</Grid> 

Così abbiamo sono vincolanti TextBlock.Text alla ActualWidthValue sull'oggetto proxy. L'oggetto proxy a sua volta fornisce ActualWidth of the Element, che è fornito da un altro Binding.

Questa non è una soluzione semplice al problema, ma è la soluzione migliore che posso immaginare per come collegarsi a ActualWidth.

Se hai spiegato un po 'di più il tuo scenario, potrebbe essere possibile trovare una soluzione più semplice. DataBinding potrebbe non essere richiesto affatto; sarebbe possibile semplicemente impostare la proprietà dal codice in un gestore di eventi SizeChanged?

+0

Ho seguito l'approccio suggerito con il gestore di eventi SizeChanged e sto ottenendo l'effetto desiderato.Per spiegare lo scenario un po 'di più avevo bisogno di legarsi alla proprietà ActualWidth come se avessi un po' di una strana progettazione dell'interfaccia utente che richiede che alcuni elementi appaiano fuori dai limiti del controllo. –

+2

Più di 5 anni e funziona ancora con WinRT ;-) - con una piccola modifica: il nuovo SizeEventHandler (Element_SizeChanged) deve essere sostituito direttamente da Element_SizeChanged. – TheEye

1

Ho testato il codice XAML aggiornato che la pubblicazione utilizzando un TestConverter per vedere quale valore viene passato alla larghezza e sta funzionando per me (sto usando VS 2010 B2). Per utilizzare il TestConverter è sufficiente impostare un punto di interruzione nel metodo Converti.

public class TestConverter : IValueConverter 
    { 

     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      return value; 
     } 

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 
     { 
      return value; 
     } 

    } 

Un valore di 150 è stata approvata nel ed il rettangolo aveva una larghezza di 150.

Si aspettava qualcosa di diverso?

+0

Si è corretto a partire da 0, tuttavia poiché ActualWidth è una proprietà di dipendenza, è necessario che ci sia una notifica quando cambia. Non riesco a collegarmi a Width poiché non è mai impostato per il controllo e di conseguenza restituisce double.NaN –

+0

Sei corretto, è una proprietà di dipendenza quindi dovrebbe cambiare. Tuttavia, stai impostando la larghezza in Xaml sopra. – Bryant

+0

Le mie scuse, che sono state trascurate da parte mia, ho appena copiato lo snippet dalla sandbox su cui stavo lavorando per risolvere il problema che sto riscontrando. Ho corretto l'errore e aggiornato l'esempio per descrivere meglio la mia situazione. –

8

Troppo tardi, lo so, ma ho appena lottato con questo problema.La mia soluzione è dichiarare il mio DependencyProperty chiamato RealWidth e aggiornarne il valore sull'evento SizeChanged. È quindi possibile eseguire il binding a RealWidth, che verrà aggiornato, a differenza della proprietà ActualWidth.

public MyControl() 
{ 
    InitializeComponent(); 
    SizeChanged += HandleSizeChanged; 
} 

public static DependencyProperty RealWidthProperty = 
    DependencyProperty.Register("RealWidth", typeof (double), 
    typeof (MyControl), 
    new PropertyMetadata(500D)); 

public double RealWidth 
{ 
    get { return (double) GetValue(RealWidthProperty); } 
    set { SetValue(RealWidthProperty, value); } 
} 

private void HandleSizeChanged(object sender, SizeChangedEventArgs e) 
{ 
    RealWidth = e.NewSize.Width; 
} 
20

Utilizzando il meccanismo di proprietà associate, proprietà che rappresentano ActualHeight e ActualWidth e aggiornato da SizeChanged evento può essere definito. Il suo utilizzo sarà simile al seguente.

<Grid local:SizeChange.IsEnabled="True" x:Name="grid1">...</Grid> 

<TextBlock Text="{Binding ElementName=grid1, 
         Path=(local:SizeChange.ActualHeight)}"/> 

dettagli tecnici possono essere trovati al seguente:

http://darutk-oboegaki.blogspot.com/2011/07/binding-actualheight-and-actualwidth.html

Il vantaggio di questa soluzione rispetto ad altri è che le proprietà associate definite nella soluzione (SizeChange.ActualHeight e SizeChange. ActualWidth) può essere utilizzato per qualsiasi FrameworkElement senza creare sottoclassi. Questa soluzione è riutilizzabile e meno invasiva.


Nel caso in cui il collegamento diventa stantio, qui è la classe SizeChange come mostrato sul link:

// Declare SizeChange class as a sub class of DependencyObject 

// because we need to register attached properties. 
public class SizeChange : DependencyObject 
{ 
    #region Attached property "IsEnabled" 

    // The name of IsEnabled property. 
    public const string IsEnabledPropertyName = "IsEnabled"; 

    // Register an attached property named "IsEnabled". 
    // Note that OnIsEnabledChanged method is called when 
    // the value of IsEnabled property is changed. 
    public static readonly DependencyProperty IsEnabledProperty 
     = DependencyProperty.RegisterAttached(
      IsEnabledPropertyName, 
      typeof(bool), 
      typeof(SizeChange), 
      new PropertyMetadata(false, OnIsEnabledChanged)); 

    // Getter of IsEnabled property. The name of this method 
    // should not be changed because the dependency system 
    // uses it. 
    public static bool GetIsEnabled(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(IsEnabledProperty); 
    } 

    // Setter of IsEnabled property. The name of this method 
    // should not be changed because the dependency system 
    // uses it. 
    public static void SetIsEnabled(DependencyObject obj, bool value) 
    { 
     obj.SetValue(IsEnabledProperty, value); 
    } 

    #endregion 

    #region Attached property "ActualHeight" 

    // The name of ActualHeight property. 
    public const string ActualHeightPropertyName = "ActualHeight"; 

    // Register an attached property named "ActualHeight". 
    // The value of this property is updated When SizeChanged 
    // event is raised. 
    public static readonly DependencyProperty ActualHeightProperty 
     = DependencyProperty.RegisterAttached(
      ActualHeightPropertyName, 
      typeof(double), 
      typeof(SizeChange), 
      null); 

    // Getter of ActualHeight property. The name of this method 
    // should not be changed because the dependency system 
    // uses it. 
    public static double GetActualHeight(DependencyObject obj) 
    { 
     return (double)obj.GetValue(ActualHeightProperty); 
    } 

    // Setter of ActualHeight property. The name of this method 
    // should not be changed because the dependency system 
    // uses it. 
    public static void SetActualHeight(DependencyObject obj, double value) 
    { 
     obj.SetValue(ActualHeightProperty, value); 
    } 

    #endregion 

    #region Attached property "ActualWidth" 

    // The name of ActualWidth property. 
    public const string ActualWidthPropertyName = "ActualWidth"; 

    // Register an attached property named "ActualWidth". 
    // The value of this property is updated When SizeChanged 
    // event is raised. 
    public static readonly DependencyProperty ActualWidthProperty 
     = DependencyProperty.RegisterAttached(
      ActualWidthPropertyName, 
      typeof(double), 
      typeof(SizeChange), 
      null); 

    // Getter of ActualWidth property. The name of this method 
    // should not be changed because the dependency system 
    // uses it. 
    public static double GetActualWidth(DependencyObject obj) 
    { 
     return (double)obj.GetValue(ActualWidthProperty); 
    } 

    // Setter of ActualWidth property. The name of this method 
    // should not be changed because the dependency system 
    // uses it. 
    public static void SetActualWidth(DependencyObject obj, double value) 
    { 
     obj.SetValue(ActualWidthProperty, value); 
    } 

    #endregion 

    // This method is called when the value of IsEnabled property 
    // is changed. If the new value is true, an event handler is 
    // added to SizeChanged event of the target element. 
    private static void OnIsEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) 
    { 
     // The given object must be a FrameworkElement instance, 
     // because we add an event handler to SizeChanged event 
     // of it. 
     var element = obj as FrameworkElement; 

     if (element == null) 
     { 
      // The given object is not an instance of FrameworkElement, 
      // meaning SizeChanged event is not available. So, nothing 
      // can be done for the object. 
      return; 
     } 

     // If IsEnabled=True 
     if (args.NewValue != null && (bool)args.NewValue == true) 
     { 
      // Attach to the element. 
      Attach(element); 
     } 
     else 
     { 
      // Detach from the element. 
      Detach(element); 
     } 
    } 

    private static void Attach(FrameworkElement element) 
    { 
     // Add an event handler to SizeChanged event of the element 

     // to take action when actual size of the element changes. 
     element.SizeChanged += HandleSizeChanged; 
    } 

    private static void Detach(FrameworkElement element) 
    { 
     // Remove the event handler from the element. 
     element.SizeChanged -= HandleSizeChanged; 
    } 

    // An event handler invoked when SizeChanged event is raised. 
    private static void HandleSizeChanged(object sender, SizeChangedEventArgs args) 
    { 
     var element = sender as FrameworkElement; 

     if (element == null) 
     { 
      return; 
     } 

     // Get the new actual height and width. 
     var width = args.NewSize.Width; 
     var height = args.NewSize.Height; 

     // Update values of SizeChange.ActualHeight and 

     // SizeChange.ActualWidth. 
     SetActualWidth(element, width); 
     SetActualHeight(element, height); 
    } 
} 
+1

Questo funziona in fase di esecuzione, ma mi dà un errore "valore non rientra nell'intervallo previsto" nel designer di Visual Studio –

5

perché non creare un semplice pannello di controllo che eredita da ContentPresenter ed effettivamente può fornisce la dimensione corrente.

public class SizeNotifyPanel : ContentPresenter 
{ 
    public static DependencyProperty SizeProperty = 
     DependencyProperty.Register("Size", 
            typeof (Size), 
            typeof (SizeNotifyPanel), 
            null); 

    public Size Size 
    { 
     get { return (Size) GetValue(SizeProperty); } 
     set { SetValue(SizeProperty, value); } 
    } 

    public SizeNotifyPanel() 
    { 
     SizeChanged += (s, e) => Size = e.NewSize; 
    } 
} 

Dovrebbe quindi essere utilizzato come wrapper per il contenuto effettivo.

<local:SizeNotifyPanel x:Name="Content"> 
    <TextBlock Text="{Binding Size.Height, ElementName=Content}" /> 
</local:SizeNotifyPanel> 

Ha funzionato per me come un fascino e sembra pulito.

+0

molto bene, utilizzando StackPanel come base risolto il mio problema. Quando usi ContentPresenter hai difficoltà a raggiungere gli elementi interni per nome e impostarne i valori. –

+0

Soluzione molto buona e pulita, Grid usato come genitore nel mio caso – deafsheep

2

Sulla base di @ darutk answer, ecco una soluzione basata su proprietà allegata che svolge il lavoro molto elegantemente.

public static class SizeBindings 
{ 
    public static readonly DependencyProperty ActualHeightProperty = 
     DependencyProperty.RegisterAttached("ActualHeight", typeof (double), typeof (SizeBindings), 
              new PropertyMetadata(0.0)); 

    public static readonly DependencyProperty ActualWidthProperty = 
     DependencyProperty.RegisterAttached("ActualWidth", typeof (Double), typeof (SizeBindings), 
              new PropertyMetadata(0.0)); 

    public static readonly DependencyProperty IsEnabledProperty = 
     DependencyProperty.RegisterAttached("IsEnabled", typeof (bool), typeof (SizeBindings), 
              new PropertyMetadata(false, HandlePropertyChanged)); 

    private static void HandlePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     var element = d as FrameworkElement; 
     if (element == null) 
     { 
      return; 
     } 

     if ((bool) e.NewValue == false) 
     { 
      element.SizeChanged -= HandleSizeChanged; 
     } 
     else 
     { 
      element.SizeChanged += HandleSizeChanged; 
     } 
    } 

    private static void HandleSizeChanged(object sender, SizeChangedEventArgs e) 
    { 
     var element = sender as FrameworkElement; 

     SetActualHeight(element, e.NewSize.Height); 
     SetActualWidth(element, e.NewSize.Width); 
    } 

    public static bool GetIsEnabled(DependencyObject obj) 
    { 
     return (bool)obj.GetValue(IsEnabledProperty); 
    } 

    public static void SetIsEnabled(DependencyObject obj, bool value) 
    { 
     obj.SetValue(IsEnabledProperty, value); 
    } 

    public static Double GetActualWidth(DependencyObject obj) 
    { 
     return (Double) obj.GetValue(ActualWidthProperty); 
    } 

    public static void SetActualWidth(DependencyObject obj, Double value) 
    { 
     obj.SetValue(ActualWidthProperty, value); 
    } 

    public static double GetActualHeight(DependencyObject obj) 
    { 
     return (double)obj.GetValue(ActualHeightProperty); 
    } 

    public static void SetActualHeight(DependencyObject obj, double value) 
    { 
     obj.SetValue(ActualHeightProperty, value); 
    } 
} 

usare in questo modo:

<Grid> 
     <Border x:Name="Border" behaviors:SizeBindings.IsEnabled="True"/> 
     <Border MinWidth="{Binding (behaviors:SizeBindings.ActualWidth), ElementName=Border}"/> 
    </Grid> 
0

Questo è un come risposta da parte che può aiutare qualcuno per il legame al ActualWidth.

Il mio processo non ha richiesto un evento di modifica, era necessario un risultato finale di un valore nel suo stato corrente. Così ho creato una proprietà di dipendenza chiamata Target sul mio controllo/processo personalizzato come FrameworkElement e l'xaml del consumatore si collegherebbe all'oggetto reale in questione.

Quando era il momento per il calcolo, il codice poteva estrarre l'oggetto effettivo ed estrarlo da esso ActualWidth.


proprietà di dipendenza per il controllo

public FrameworkElement Target 
{ 
    get { return (FrameworkElement)GetValue(TargetProperty);} 
    set { SetValue(TargetProperty, value);} 
} 

// Using a DependencyProperty as the backing store for Target. 
// This enables animation, styling, binding, general access etc... 
public static readonly DependencyProperty TargetProperty = 
    DependencyProperty.Register("Target", typeof(FrameworkElement), 
           typeof(ThicknessWrapper), 
           new PropertyMetadata(null, OnTargetChanged)); 

XAML sul lato dei consumatori che mostra un legame con un rettangolo

<local:ThicknessWrapper Target="{Binding ElementName=thePanel}"/> 

<Rectangle x:Name="thePanel" HorizontalAlignment="Stretch" Height="20" Fill="Blue"/> 

Codice acquisisce

double width; 

if (Target != null) 
    width = Target.ActualWidth; // Gets the current value. 
0

in base alla risposta KeithMahoney s', funziona bene sul mio UWP App e risolve il mio problema. Tuttavia, non riesco a vedere il mio controllo in fase di progettazione poiché entrambi i valori iniziali di ActualWidthValue e ActualHeightValue non sono forniti in fase di progettazione. Sebbene funzioni bene in fase di esecuzione, è scomodo progettare il layout del mio controllo. Con una piccola modifica, questo problema può essere risolto.

  1. Nel suo codice C# per entrambe le proprietà ActualWidthValue e ActualHeightValue, aggiungere

    insieme {;}

    per farci in grado di fornire valori fittizi dal codice XAML. Sebbene non sia utile per il tempo di esecuzione, può essere utilizzato per la progettazione.

  2. Nella dichiarazione di Risorse del suo codice XAML, fornire c: ActualSizePropertyProxy valori adatti per ActualWidthValue e ActualHeightValue come

    ActualHeightValue = "800" ActualWidthValue = "400 "

    Quindi mostrerà un controllo 400x800 in fase di progettazione.

Problemi correlati