绑定AvalonDock LayoutAnchorable IsVisible属性

时间:2020-08-29 08:52:40

标签: c# wpf binding

我正在尝试将AvalonDock LayoutAnchorables绑定到WPF中各自的菜单项。如果在菜单中选中,则可锚定项应可见。如果未在菜单中选中,则应隐藏可锚定对象。

IsCheckedIsVisible都是布尔值,因此我不希望需要转换器。我可以将LayoutAnchorable IsVisible属性设置为TrueFalse,并且行为在设计视图中是预期的。

但是,如果尝试按以下方式实现绑定,则会出现错误

不能在类型的“ IsVisible”属性上设置“ Binding” “ LayoutAnchorable”。只能在 DependencyObject的DependencyProperty。

问题在这里:

<dock:LayoutAnchorable ContentId="content1" IsVisible="{Binding IsChecked, ElementName=mnuPane1}" x:Name="anchorable1" IsSelected="True">

我该怎么做?

<Window x:Class="TestAvalonBinding.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:dock="http://schemas.xceed.com/wpf/xaml/avalondock"
    mc:Ignorable="d"
    Title="MainWindow"
    Height="450"
    Width="800">
<Grid>

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <!-- Menu -->
    <Menu Height="18" HorizontalAlignment="Stretch" Name="menu1" VerticalAlignment="Top" Grid.Row="0">
        <MenuItem Header="File">
            <MenuItem Header="_Foo1" Name="mnuPane1" IsCheckable="True">
            </MenuItem>
            <MenuItem Header="Foo2" Name="mnuPane2" IsCheckable="True">
            </MenuItem>
        </MenuItem>
    </Menu>

    <!-- AvalonDock -->
    <dock:DockingManager x:Name="Dockman" DockPanel.Dock="Left" Grid.Row="1" >

        <dock:LayoutRoot x:Name="_layoutRoot">
            <dock:LayoutPanel Orientation="Horizontal">
                <dock:LayoutAnchorablePaneGroup Orientation="Vertical">
                    <dock:LayoutAnchorablePane FloatingWidth="150" FloatingHeight="150" FloatingLeft="100" FloatingTop="300">
                        <dock:LayoutAnchorable ContentId="content1" IsVisible="{Binding IsChecked, ElementName=mnuPane1}" x:Name="anchorable1" IsSelected="True">
                            <GroupBox Header="Foo1"/>
                        </dock:LayoutAnchorable>
                    </dock:LayoutAnchorablePane>
                    <dock:LayoutAnchorablePane FloatingWidth="150" FloatingHeight="150" FloatingLeft="100" FloatingTop="300">
                        <dock:LayoutAnchorable ContentId="content2" x:Name="anchorable2" IsSelected="True">
                            <GroupBox Header="Foo2"/>
                        </dock:LayoutAnchorable>
                    </dock:LayoutAnchorablePane>
                </dock:LayoutAnchorablePaneGroup>
            </dock:LayoutPanel>
        </dock:LayoutRoot>
    </dock:DockingManager>
    
</Grid>
</Window>

更新

我对BionicCode的回答的实现。我剩下的问题是,如果我关闭窗格,则菜单项保持选中状态。

XAML

<Grid>

    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <!-- Menu -->
    <Menu Height="18" HorizontalAlignment="Stretch" Name="menu1" VerticalAlignment="Top" Grid.Row="0">
        <MenuItem Header="File">
            <MenuItem Header="_Foo1" Name="mnuPane1" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=local:MainWindow}, Path=IsAnchorable1Visible}"/>
            <MenuItem Header="Foo2" Name="mnuPane2" IsCheckable="True" IsChecked="{Binding RelativeSource={RelativeSource AncestorType=local:MainWindow}, Path=IsAnchorable2Visible}"/>
        </MenuItem>
    </Menu>

    <!-- AvalonDock -->
    <dock:DockingManager x:Name="Dockman" DockPanel.Dock="Left" Grid.Row="1" >
        <dock:LayoutRoot x:Name="_layoutRoot">
            <dock:LayoutPanel Orientation="Horizontal">
                <dock:LayoutAnchorablePaneGroup Orientation="Vertical">
                    <dock:LayoutAnchorablePane FloatingWidth="150" FloatingHeight="150" FloatingLeft="100" FloatingTop="300">
                        <dock:LayoutAnchorable ContentId="content1" x:Name="anchorable1" IsSelected="True" >
                            <GroupBox Header="Foo1"/>
                        </dock:LayoutAnchorable>
                    </dock:LayoutAnchorablePane>
                    <dock:LayoutAnchorablePane FloatingWidth="150" FloatingHeight="150" FloatingLeft="100" FloatingTop="300">
                        <dock:LayoutAnchorable ContentId="content2" x:Name="anchorable2" IsSelected="True" >
                            <GroupBox Header="Foo2"/>
                        </dock:LayoutAnchorable>
                    </dock:LayoutAnchorablePane>
                </dock:LayoutAnchorablePaneGroup>
            </dock:LayoutPanel>
        </dock:LayoutRoot>
    </dock:DockingManager>

</Grid>

背后的代码

partial class MainWindow : Window
{
    public static readonly DependencyProperty IsAnchorable1VisibleProperty = DependencyProperty.Register(
      "IsAnchorable1Visible",
      typeof(bool),
      typeof(MainWindow),
      new PropertyMetadata(default(bool), MainWindow.OnIsAnchorable1VisibleChanged));

    public static readonly DependencyProperty IsAnchorable2VisibleProperty = DependencyProperty.Register(
      "IsAnchorable2Visible",
      typeof(bool),
      typeof(MainWindow),
      new PropertyMetadata(default(bool), MainWindow.OnIsAnchorable2VisibleChanged));

    public bool IsAnchorable1Visible
    {
        get => (bool)GetValue(MainWindow.IsAnchorable1VisibleProperty);
        set => SetValue(MainWindow.IsAnchorable1VisibleProperty, value);
    }
    public bool IsAnchorable2Visible
    {
        get => (bool)GetValue(MainWindow.IsAnchorable2VisibleProperty);
        set => SetValue(MainWindow.IsAnchorable2VisibleProperty, value);
    }

    public MainWindow()
    {
        InitializeComponent();
        this.IsAnchorable1Visible = true;
        this.IsAnchorable2Visible = true;
    }

    private static void OnIsAnchorable1VisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        (d as MainWindow).anchorable1.IsVisible = (bool)e.NewValue;
    }
    private static void OnIsAnchorable2VisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        (d as MainWindow).anchorable2.IsVisible = (bool)e.NewValue;
    }
}

2 个答案:

答案 0 :(得分:1)

AvalonDock XAML布局元素既不是控件也不是UIElement的派生。它们充当普通模型(尽管它们扩展了DependencyObject)。
LayoutAnchorable的属性未实现为DependencyProperty,而是实现了INotifyPropertyChanged(如前所述,布局元素用作控件的视图模型)。因此,他们不支持数据出价(作为绑定目标)。

这些XAML布局元素中的每个元素都有一个对应的控件,该控件实际上将以布局元素显示为DataContext。名称等于带有后缀 Control 的布局元素的名称。如果要将这些控件或项目容器(例如LayoutAnchorableItem)连接到视图模型,则必须创建一个针对此容器的Style。下一个缺陷是此容器的DataContext不是控件要显示的数据模型,而是控件的内部模型。要进入您的视图模型,您需要访问例如LayoutAnchorableControl.LayoutItem.Model(因为LayoutAnchorableControl.DataContextLayoutAnchorable)。

作者显然迷路了,因为他们太渴望使用MVVM实现 control 本身(如他们的文档所述),而忘记了以MVVM客户端 application 为目标。他们打破了常见的WPF模式。在外部看起来不错,但在内部看起来不太好。

要解决您的问题,必须在视图上引入一个中间依赖项属性。然后,已注册属性更改的回调将委派可见性以切换可定位对象的可见性。
还需要注意的是,AvalonDock的作者没有使用UIElement.Visibility来处理可见性。他们引入了独立于框架属性的自定义可见性逻辑。

如前所述,始终存在纯模型驱动的方法,您可以通过提供ILayoutUpdateStrategy实现来布局初始视图。然后,您可以定义样式以连接视图和视图模型。使用XAML布局元素对视图进行硬编码会在更高级的场景中带来某些不便。

LayoutAnchorable公开了Show()Close()方法或IsVisible属性以处理可见性。您还可以在访问LayoutAnchorableControl.LayoutItem时绑定到命令(例如,从ControlTemplate内部),该命令将返回LayoutAnchorableItem。此LayoutAnchorableItem暴露了HideCommand

MainWindow.xaml

<Window>
  <Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>

    <!-- Menu -->
    <Menu Grid.Row="0">
      <MenuItem Header="File">
        <MenuItem Header="_Foo1" 
                  IsCheckable="True"
                  IsChecked="{Binding RelativeSource={RelativeSource AncestorType=MainWindow}, Path=IsAnchorable1Visible}" />
      </MenuItem>
    </Menu>

    <!-- AvalonDock -->
    <dock:DockingManager Grid.Row="1" >
      <dock:LayoutRoot>
        <dock:LayoutPanel>
          <dock:LayoutAnchorablePaneGroup>
            <dock:LayoutAnchorablePane>
              <dock:LayoutAnchorable x:Name="Anchorable1"
                                     Hidden="Anchorable1_OnHidden">
                <GroupBox Header="Foo1" />
              </dock:LayoutAnchorable>
            </dock:LayoutAnchorablePane>
          </dock:LayoutAnchorablePaneGroup>
        </dock:LayoutPanel>
      </dock:LayoutRoot>
    </dock:DockingManager>    
  </Grid>
</Window>

MainWindow.xaml.cs

partial class MainWindow : Window
{
  public static readonly DependencyProperty IsAnchorable1VisibleProperty = DependencyProperty.Register(
    "IsAnchorable1Visible",
    typeof(bool),
    typeof(MainWindow),
    new PropertyMetadata(default(bool), MainWindow.OnIsAnchorable1VisibleChanged));

  public bool IsAnchorable1Visible
  {
    get => (bool) GetValue(MainWindow.IsAnchorable1VisibleProperty);
    set => SetValue(MainWindow.IsAnchorable1VisibleProperty, value);
  }

  public MainWindow()
  {
    InitializeComponent();
    this.IsAnchorable1Visible = true;
  }

  private static void OnIsAnchorable1VisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  { 
    (d as MainWindow).Anchorable1.IsVisible = (bool) e.NewValue;
  }

  private void Anchorable1_OnHidden(object sender, EventArgs e) => this.IsAnchorable1Visible = false;
}

答案 1 :(得分:1)

绑定有两个主要问题。

  1. IsVisible属性不是DependencyProperty,而只是CLR属性,因此您无法绑定它
  2. LayoutAnochorable不是视觉树的一部分,因此ElementNameRelativeSource绑定不起作用,您将在输出窗口中看到相应的绑定错误

我不确定是否有特定的设计选择或限制,以使IsVisible属性成为依赖属性,但是您可以通过创建附加属性来解决此问题。可以绑定此属性,并在IsVisible更改时设置CLR属性LayoutAnchorable

public class LayoutAnchorableProperties
{
   public static readonly DependencyProperty IsVisibleProperty = DependencyProperty.RegisterAttached(
      "IsVisible", typeof(bool), typeof(LayoutAnchorableProperties), new PropertyMetadata(true, OnIsVisibleChanged));

   public static bool GetIsVisible(DependencyObject dependencyObject)
   {
      return (bool)dependencyObject.GetValue(IsVisibleProperty);
   }

   public static void SetIsVisible(DependencyObject dependencyObject, bool value)
   {
      dependencyObject.SetValue(IsVisibleProperty, value);
   }

   private static void OnIsVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
   {
      if (d is LayoutAnchorable layoutAnchorable)
         layoutAnchorable.IsVisible = (bool)e.NewValue;
   }
}

您可以在XAML中绑定此属性,但是可以这样说,由于LayoutAnchorable不在可视树中,因此无法使用。 DataGrid列会发生相同的问题。在this related post中,您将找到我们将使用的BindingProxy类的解决方法。请将此课程复制到您的项目中。

在您的DockingManager.Resources中创建绑定代理的实例。它用于访问菜单项。

<dock:DockingManager x:Name="Dockman" DockPanel.Dock="Left" Grid.Row="1">
   <dock:DockingManager.Resources>
      <local:BindingProxy x:Key="mnuPane1Proxy" Data="{Binding ElementName=mnuPane1}"/>
   </dock:DockingManager.Resources>
   <!-- ...other XAML code. -->
</dock:DockingManager>

删除旧的IsVisible绑定。使用mnuPane1Proxy将绑定添加到附加属性。

<xcad:LayoutAnchorable ContentId="content1"
                       x:Name="anchorable1"
                       IsSelected="True"
                       local:LayoutAnchorableProperties.IsVisible="{Binding Data.IsChecked, Source={StaticResource mnuPane1Proxy}}">

最后,将菜单项中的默认IsChecked状态设置为true,因为这是IsVisible的默认状态,并且由于设置了默认值,绑定不会在初始化时更新附加属性中的值,可以防止由于控件未完全初始化而引发InvalidOperationException

<MenuItem Header="_Foo1" Name="mnuPane1" IsCheckable="True" IsChecked="True">