WPF: Associazione a ObservableCollection in ControlTemplate non viene aggiornato
-
27-09-2019 - |
Domanda
ho creato un ControlTemplate
per il mio MyControl
controllo personalizzato.
deriva da MyControl
System.Windows.Controls.Control
e definisce la seguente public ObservableCollection<MyControl> Children{ get; protected set; }
struttura.
Per visualizzare i controlli figlio nidificati Sto utilizzando un ItemsControl
(StackPanel
) che è circondato da un GroupBox
. Se non ci sono controlli figlio, voglio nascondere il GroupBox
.
Tutto funziona bene all'avvio dell'applicazione: La casella di gruppo e controlli figlio vengono visualizzate se la proprietà bambini inizialmente conteneva almeno un elemento. Nell'altro caso è nascosto.
Il problema inizia quando l'utente aggiunge un controllo figlio per un insieme vuoto. la visibilità del GroupBox
è ancora crollato. Lo stesso problema si verifica quando l'ultimo controllo figlio viene rimosso dalla collezione. Il GroupBox
è ancora visibile.
Un altro sintomo è che il convertitore HideEmptyEnumerationConverter
non viene chiamato.
L'aggiunta / rimozione di controlli figlio di opere non collezioni vuoti come previsto.
Che cosa è con il seguente vincolante? Ovviamente funziona una volta, ma non viene aggiornato, anche se la collezione che sto legame a è di tipo ObservableCollection
.
<!-- Converter for hiding empty enumerations -->
<Common:HideEmptyEnumerationConverter x:Key="hideEmptyEnumerationConverter"/>
<!--- ... --->
<ControlTemplate TargetType="{x:Type MyControl}">
<!-- ... other stuff that works ... -->
<!-- Child components -->
<GroupBox Header="Children"
Visibility="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Children, Converter={StaticResource hideEmptyEnumerationConverter}}">
<ItemsControl ItemsSource="{TemplateBinding Children}"/>
</GroupBox>
</ControlTemplate>
.
[ValueConversion(typeof (IEnumerable), typeof (Visibility))]
public class HideEmptyEnumerationConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int itemCount = ((IEnumerable) value).Cast<object>().Count();
return itemCount == 0 ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Un altro, questione più generale: come si fa a ragazzi attacchi di debug? Ho trovato questo ( http://bea.stollnitz.com/blog/?p=52), ma ancora lo trovo molto difficile da fare.
Sono contento per qualsiasi aiuto o suggerimento.
Soluzione
Il problema è che la vostra proprietà Children
in sé non cambia mai, solo il suo contenuto. Dal momento che il valore della proprietà non cambia, il legame non è rivalutata. Quello che dovete fare è legano alla proprietà Count
della collezione. Il modo più semplice è possibile raggiungere questo obiettivo è con un DataTrigger
nel modello:
<ControlTemplate TargetType="{x:Type MyControl}">
<!-- ... other stuff that works ... -->
<!-- Child components -->
<GroupBox x:Name="gb" Header="Children">
<ItemsControl ItemsSource="{TemplateBinding Children}"/>
</GroupBox>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Children.Count, RelativeSource={RelativeSource TemplatedParent}}"
Value="0">
<Setter TargetName="gb" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Altri suggerimenti
È necessario avvisare ogni volta che il numero di elementi nei vostri bambini modifiche alle proprietà. Si può fare implementando l'interfaccia INotifyPropertyChanged, registrati per evento CollectionChanged di raccolta dei bambini e rilancio PropertyChanged da lì.
Esempio:
public class MyControl : Control, INotifyPropertyChanged
{
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl), new FrameworkPropertyMetadata(typeof(MyControl)));
}
public ObservableCollection<UIElement> Children
{
get { return (ObservableCollection<UIElement>)GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
// Using a DependencyProperty as the backing store for Children. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ChildrenProperty =
DependencyProperty.Register("Children", typeof(ObservableCollection<UIElement>), typeof(MyControl), new UIPropertyMetadata(0));
public MyControl()
{
Children = new ObservableCollection<UIElement>();
Children.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Children_CollectionChanged);
}
void Children_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("Children");
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(String propertyName)
{
PropertyChangedEventHandler temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(propertyName));
}
}
}