【问题标题】:Binding an AvalonDock LayoutAnchorable IsVisible property绑定 AvalonDock LayoutAnchorable IsVisible 属性
【发布时间】:2020-08-29 08:52:40
【问题描述】:

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

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

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

不能在类型的“IsVisible”属性上设置“绑定” '布局锚定'。 “绑定”只能设置在 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;
    }
}

【问题讨论】:

    标签: c# wpf binding


    【解决方案1】:

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

    这些 XAML 布局元素中的每一个都有一个相应的控件,该控件将使用布局元素实际呈现为DataContext。这些名称与附加了 Control 后缀的布局元素的名称相同。如果您想将这些控件或项目容器(例如 LayoutAnchorableItem)连接到您的视图模型,则必须创建一个以该容器为目标的 Style。下一个缺陷是这个容器的DataContext 不是控件要显示的数据模型,而是控件的内部模型。要访问您的视图模型,您需要访问例如LayoutAnchorableControl.LayoutItem.Model(因为LayoutAnchorableControl.DataContextLayoutAnchorable)。

    作者显然迷失了方向,因为他们太急于使用 MVVM 实现 控件(如他们的文档中所述)而忘记定位 MVVM 客户端应用程序。他们打破了常见的 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;
    }
    

    【讨论】:

    • 感谢您的帮助。我正在尝试测试/实现此处给出的任何一个答案,但由于 XAML 解析异常而无法执行代码。我收到了 NotImplemented'Initialization of 'Xceed.Wpf.AvalonDock.Controls.AnchorablePaneTitle' 异常。我已经尝试从我的真实应用程序中添加大量代码(我没有收到此错误)并将其减少到最低限度,但到目前为止没有成功。我们知道导致此错误的原因吗?
    • (我所指的错误可以重现 - 至少对我来说 - 使用我的问题中显示的代码。只需从第 35 行删除 IsVisible 属性并执行它。)跨度>
    • 我刚刚执行了你的代码。原始代码抛出无效绑定异常(当然)。然后我按照指示从LayoutAnchorable 中删除了IsVisible 属性,一切都编译良好并执行显示实际视图。在这种情况下,我还测试了我的解决方案,它也很有效。我刚刚从构造函数中将IsAnchorable1Visible 的初始值设置为true(我已经更新了示例)。您描述的问题必须由您帖子中未显示的一些代码引入。您是否在某处设计/操作 AnchorablePaneTitle
    • 创建一个空项目并在删除 IsVisible 属性后从您的问题中运行/构建代码 - 您将看到它有效。使用文本搜索查找任何“AnchorablePaneTitle”。另外这个异常是在哪里抛出的,哪一行代码?看起来它可能会在 Xceed 库代码中抛出。当应用程序因异常而暂停时,请检查堆栈跟踪。后退一步,直到到达属于您的代码行。此行正在触发异常。不要忘记在您的 XAML 文件中搜索任何针对 AnchorablePaneTitle 的样式。
    • 再次感谢。 :-) 我重新创建了项目,仍然得到相同的结果。除了这个问题的 XAML 之外,没有其他内容。堆栈跟踪只包含 System 和 Win32 方法,所以我一点也不聪明。但通过反复试验解决。它与 Xceed DLL 有关(我猜是版本?)。我用我在其他项目中使用过的替换了我的测试项目中的 Xceed DLL,然后它就可以工作了。我似乎在本地只有一个版本(即时间戳都是相同的),所以不确定到底发生了什么。可能是我在创建测试项目时从 Nuget 获取它们。
    【解决方案2】:

    您的绑定有两个主要问题。

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

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

    【讨论】:

    • 感谢您的帮助和详尽的解释。就菜单而言,这有效。不过,这不是双向的。如果我关闭窗格(通过单击“X”),菜单项仍处于选中状态。如果我没记错的话,我读过关闭窗格会折叠它,而不是将IsVisible 设置为false。可能是原因(?)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-12-01
    • 2021-02-21
    • 2012-06-10
    • 2022-06-20
    • 1970-01-01
    • 2011-02-27
    相关资源
    最近更新 更多