I'm having troubles with triggers for a Child-Parent relation in my ViewModel.
Consider the following ViewModel (implementsINotifyPropertyChanged
) and View:
ViewModelType
+ Items: ObservableCollection<ViewModelType>
+ IsVisible: bool
+ Text: string
ViewType
+ Visibility: Visibility
+ Header: string
I have a DataTemplate to bind the 2:
<DataTemplate DataType="{x:Type vm:ViewModelType}">
<views:ViewType Header="{Binding Text}"/>
</DataTemplate>
And a style for the View:
<Style TargetType="{x:Type views:ViewType}">
<Setter Property="Visibility" Value="{Binding IsVisible, Converter={StaticResource Bool2Visibility}}"/>
</Style>
This all works beautifully.
Now, what I want to achieve is that if from ViewModelTypeInstanceA all ViewModelType.Items
have their IsVisible
property set to false
, I want ViewModelTypeInstanceA corresponding ViewType
to have its Visibility
property set to Visibility.Collapsed
**.
I have tried DataTriggers, converters and all but I don't think I can use something like AncestorType in triggers to non referable parent? It seems impossible to trigger a parent property. Perhaps it's possible for an element to observe all it's children's IsVisisble properties?
Conditions:
1. I prefer to NOT alter the ControlTemplate (it's from a diff lib).
2. Modifying the ViewModel structure isn't an option either (compatibility issues). That means I cannot maintain a property Parent
on item creation or alter the Collection type as Eli suggested.
3. I really prefer an elegant solution.
**Ofcourse depending on the children's ViewType's VisibilityProperty is also OK, if it can be set through styling.
Not the cleanest, but the best that I could think of I guess. I'm (ab)using the physical parent's DataContext.
ViewModelType.HasVisibleChild
-property and a EvaluateHasVisibleChildValue()
method.
internal void EvaluateHasVisibleChild()
{
foreach(ViewModelType node in Items)
// Conditions for ultimate visibility
if (node.IsVisible && node.HasVisibleChild)
{
HasVisibleChild = true;
return;
}
HasVisibleChild = false;
}
ViewType
ancestor:
<Style TargetType="{x:Type views:ViewType}">
<Setter.Value>
<MultiBinding Converter="{StaticResource ConditionalVisibilityConverter}">
<Binding Path="IsVisible"/>
<Binding Path="HasVisibleChild"/>
<Binding Path="." RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type views:ViewType}}"/>
</MultiBinding>
</Setter.Value>
</Style>
public class ConditionalVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object paramater, CultureInfo culture)
{
// Has no ancestor safety check
if (values[2] != DependencyProperty.UnsetValue)
// Sneak in an update of viewmodelparent via physical parent.
(((values[2] as FrameworkElement).DataContext) as ViewModelType).EvaluateHasVisibleChild();
return (bool)values[0] && (bool)values[1] ? Visibility.Visible : Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object paramweter, CultureInfo cultuer)
{
throw new NotImplementedException();
}
}
In practise I have multiple ViewTypes as ancestors, so I added multiple Bindings for various ancestorTypes. I also considered adding an Interface to various the ViewTypes, but nuh-uh.
N.b. this obviously requires that exactly 1 ViewType is used per ViewModelType, otherwise the FindAncestorType might get unexpected results.