我正在尝试将AvalonDock LayoutAnchorables
绑定到WPF中各自的菜单项。如果在菜单中选中,则可锚定项应可见。如果未在菜单中选中,则应隐藏可锚定对象。
IsChecked
和IsVisible
都是布尔值,因此我不希望需要转换器。我可以将LayoutAnchorable
IsVisible
属性设置为True
或False
,并且行为在设计视图中是预期的。
但是,如果尝试按以下方式实现绑定,则会出现错误
不能在类型的“ 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;
}
}
答案 0 :(得分:1)
AvalonDock XAML布局元素既不是控件也不是UIElement
的派生。它们充当普通模型(尽管它们扩展了DependencyObject
)。
LayoutAnchorable
的属性未实现为DependencyProperty
,而是实现了INotifyPropertyChanged
(如前所述,布局元素用作控件的视图模型)。因此,他们不支持数据出价(作为绑定目标)。
这些XAML布局元素中的每个元素都有一个对应的控件,该控件实际上将以布局元素显示为DataContext
。名称等于带有后缀 Control 的布局元素的名称。如果要将这些控件或项目容器(例如LayoutAnchorableItem
)连接到视图模型,则必须创建一个针对此容器的Style
。下一个缺陷是此容器的DataContext
不是控件要显示的数据模型,而是控件的内部模型。要进入您的视图模型,您需要访问例如LayoutAnchorableControl.LayoutItem.Model
(因为LayoutAnchorableControl.DataContext
是LayoutAnchorable
)。
作者显然迷路了,因为他们太渴望使用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)
绑定有两个主要问题。
IsVisible
属性不是DependencyProperty
,而只是CLR属性,因此您无法绑定它LayoutAnochorable
不是视觉树的一部分,因此ElementName
和RelativeSource
绑定不起作用,您将在输出窗口中看到相应的绑定错误我不确定是否有特定的设计选择或限制,以使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">