clcto clcto - 24 days ago 13
C# Question

Binding to LayoutAnchorableItem Visibility in AvalonDock 2

I am attempting to bind the

Visibility
of
LayoutAnchorableItem
to a boolean in the ViewModel so that I can programmatically show and hide the anchorable:

<UserControl.Resources>
<avalon:BoolToVisibilityConverter x:Key="btvc"/>
</UserControl.Resources>

<avalon:DockingManager>
<avalon:DockingManager.LayoutItemContainerStyleSelector>
<ws:WorkspaceStyleSelector>
<ws:WorkspaceStyleSelector.AnchorableStyle>
<Style TargetType="{x:Type avalon:LayoutAnchorableItem}">
<!-- ... -->
<Setter Property="Visibility" Value="{Binding Model.IsVisible, Converter={StaticResource btvc}, Mode=TwoWay}"/>
</Style>
</ws:WorkspaceStyleSelector.AnchorableStyle>
</ws:WorkspaceStyleSelector>
</avalon:DockingManager.LayoutItemContainerStyleSelector>

<!-- ... -->

</avalon:DockingManager>


However, whenever I hide an anchorable, an exception is thrown:


Object reference is not set to an instance.


at Xceed.Wpf.AvalonDock.Layout.LayoutContent.Close() in ...\Xceed.Wpf.AvalonDock\Layout\LayoutContent.cs:line 346
at Xceed.Wpf.AvalonDock.Controls.LayoutItem.OnVisibilityChanged() in ...\Xceed.Wpf.AvalonDock\Controls\LayoutItem.cs:line 310
at Xceed.Wpf.AvalonDock.Controls.LayoutAnchorableItem.OnVisibilityChanged() in ...\Xceed.Wpf.AvalonDock\Controls\LayoutAnchorableItem.cs:line 299
at Xceed.Wpf.AvalonDock.Controls.LayoutItem.OnVisibilityChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) in ...\Xceed.Wpf.AvalonDock\Controls\LayoutItem.cs:line 303
at Xceed.Wpf.AvalonDock.Controls.LayoutItem.<.cctor>b__1(DependencyObject s, DependencyPropertyChangedEventArgs e) in ...\Xceed.Wpf.AvalonDock\Controls\LayoutItem.cs:line 37
at System.Windows.PropertyChangedCallback.Invoke(DependencyObject d, DependencyPropertyChangedEventArgs e)
at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e)
...


After commenting out the binding to visibility, the anchorable is hidden as expected.

Answer

tl;dr

You need to add a ConverterParameter of value Visibility.Hidden to the Binding:

<Setter Property="Visibility" Value="{Binding Model.IsVisible, ConverterParameter={x:Static Visibility.Hidden}, Converter={StaticResource btvc}, Mode=TwoWay}"/>

The converter parameter is the Visibility that is returned when the boolean is false, and Hidden means the anchorable is hidden.

Full Answer

If we look at LayoutContent.Close(), it is marked with the comment:

Please note that usually the anchorable is only hidden (not closed). By default when user click the X button it only hides the content.

So this should not have been called. Looking at the stacktrace, this is called from:

// LayoutItem class.
protected virtual void OnVisibilityChanged()
{
    if (LayoutElement != null &&
        Visibility == System.Windows.Visibility.Collapsed)
        LayoutElement.Close();
}

According to Microsoft, System.Windows.Visibility.Collapsed means that the item is not visible and space is not reserved for it during layout. This sounds like what is happening with the anchorables when we click the X to hide them (and this is probably happening somewhere up the visual tree). But then why do the comments say that this is not normally called for anchorables? If we look at LayoutAnchorableItem.OnVisibilityChanged():

protected override void OnVisibilityChanged()
{
    if (_anchorable != null && _anchorable.Root != null)
    {
        if (_visibilityReentrantFlag.CanEnter)
        {
            using (_visibilityReentrantFlag.Enter())
            {
                if (Visibility == System.Windows.Visibility.Hidden)
                    _anchorable.Hide(false);
                else if (Visibility == System.Windows.Visibility.Visible)
                    _anchorable.Show();
            }
        }
    }

    base.OnVisibilityChanged();
}

it is clear that AvalonDock uses the Visibility.Hidden value to indicate that the anchorable is hidden. (This was a little confusing to me since Microsoft states that Hidden hides the element but reserves space in the layout which is not how the anchorable behaves when you hide it.) So why is the Visibility Collapsed instead of Hidden? The answer lies in the BoolToVisibilityConverter.

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    if (value is bool && targetType == typeof(Visibility))
    {
        bool val = (bool)value;
        if (val)
            return Visibility.Visible;
        else
            if (parameter != null && parameter is Visibility)
                return parameter;
            else
                return Visibility.Collapsed;
    }

    // ...
}

Unless a parameter of type Visibility is passed, Visibility.Collapsed is used when the boolean is false. We want false to mean Visibility.Hidden so we set that as the ConverterParameter

<Setter Property="Visibility" Value="{Binding Model.IsVisible, ConverterParameter={x:Static Visibility.Hidden}, Converter={StaticResource btvc}, Mode=TwoWay}"/>
Comments