Binden einer AvalonDock LayoutAnchorable IsVisible-Eigenschaft
Ich versuche, AvalonDock LayoutAnchorablesan die jeweiligen Menüpunkte in WPF zu binden . Wenn im Menü aktiviert, sollte der Veranker sichtbar sein. Wenn dies im Menü nicht aktiviert ist, sollte der Ankerplatz ausgeblendet werden.
Beide IsCheckedund IsVisiblesind boolesch, daher würde ich nicht erwarten, dass ein Konverter erforderlich ist. Ich kann die LayoutAnchorable IsVisibleEigenschaft auf Trueoder setzen False, und das Verhalten ist in der Entwurfsansicht wie erwartet.
Wenn ich jedoch versuche, die Bindung wie folgt zu implementieren, wird der Fehler angezeigt
'Bindung' kann nicht für die Eigenschaft 'IsVisible' vom Typ 'LayoutAnchorable' festgelegt werden. Eine 'Bindung' kann nur für eine DependencyProperty eines DependencyObject festgelegt werden.
Das Problem ist hier:
<dock:LayoutAnchorable ContentId="content1" IsVisible="{Binding IsChecked, ElementName=mnuPane1}" x:Name="anchorable1" IsSelected="True">
Wie kann ich das machen?
<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>
Aktualisieren:
Meine Implementierung der Antwort von BionicCode. Mein verbleibendes Problem ist, dass der Menüpunkt aktiviert bleibt, wenn ich einen Bereich schließe.
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>
Code dahinter
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;
}
}
Antworten
Die AvalonDock XAML-Layoutelemente sind weder Steuerelemente noch abgeleitet UIElement. Sie dienen als einfache Modelle (obwohl sie sich erweitern DependencyObject).
Die Eigenschaften von LayoutAnchorablewerden nicht als implementiert DependencyProperty, sondern implementiert INotifyPropertyChanged(wie bereits erwähnt, dienen die Layoutelemente als Ansichtsmodell des Steuerelements). Daher unterstützen sie kein Daten-Biding (als Bindungsziel).
Jedes dieser XAML-Layoutelemente verfügt über ein entsprechendes Steuerelement, das tatsächlich mit dem Layoutelement als gerendert wird DataContext. Die Namen entsprechen dem Namen des Layoutelements mit dem angehängten Control- Suffix. Wenn Sie diese Steuerelemente oder Elementcontainer beispielsweise LayoutAnchorableItemmit Ihrem Ansichtsmodell verbinden möchten , müssen Sie eine erstellen Style, die auf diesen Container abzielt. Der nächste Fehler besteht darin, dass es sich DataContextbei diesem Container nicht um Ihr Datenmodell handelt, das vom Steuerelement angezeigt werden soll, sondern um das interne Modell des Steuerelements. Um zu Ihrem Ansichtsmodell zu gelangen, müssten Sie z. B. zugreifen LayoutAnchorableControl.LayoutItem.Model(weil das das LayoutAnchorableControl.DataContextist LayoutAnchorable).
Die Autoren haben sich offensichtlich verlaufen, als sie zu eifrig waren, das Steuerelement selbst mithilfe von MVVM (wie in ihren Dokumenten angegeben) zu implementieren, und vergessen, auf die MVVM-Clientanwendung abzuzielen . Sie haben das übliche WPF-Muster gebrochen. Sieht außen gut aus, innen aber nicht so gut.
Um Ihr Problem zu lösen, müssen Sie in Ihrer Ansicht eine Zwischenabhängigkeitseigenschaft einführen. Ein Rückruf einer geänderten Eigenschaft würde dann die Sichtbarkeit delegieren, um die Sichtbarkeit des Verankerers umzuschalten.
Es ist auch wichtig zu beachten, dass die Autoren von AvalonDock das nicht verwendet haben UIElement.Visibility, um die Sichtbarkeit zu handhaben. Sie führten eine benutzerdefinierte Sichtbarkeitslogik unabhängig von der Framework-Eigenschaft ein.
Wie bereits erwähnt, gibt es immer den rein modellgetriebenen Ansatz, bei dem Sie die anfängliche Ansicht durch Bereitstellung einer ILayoutUpdateStrategyImplementierung gestalten. Anschließend definieren Sie Stile, um die Ansicht zu verkabeln und Modelle anzuzeigen. Das Hardcodieren der Ansicht mithilfe der XAML-Layoutelemente führt in fortgeschritteneren Szenarien zu gewissen Unannehmlichkeiten.
LayoutAnchorablemacht eine Show()and- Close()Methode oder die IsVisibleEigenschaft verfügbar, um die Sichtbarkeit zu handhaben. Sie können beim Zugriff auch an einen Befehl binden LayoutAnchorableControl.LayoutItem(z. B. aus a heraus ControlTemplate), der a zurückgibt LayoutAnchorableItem. Dies LayoutAnchorableItemlegt a 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;
}
Es gibt zwei Hauptprobleme mit Ihren Bindungen.
- Die
IsVisibleEigenschaft ist keineDependencyProperty, sondern nur eine CLR-Eigenschaft, sodass Sie sie nicht binden können - A
LayoutAnochorableist nicht Teil der visuellen Struktur, soElementNameundRelativeSourceBindungen nicht funktionieren, finden Sie die entsprechenden Bindungs Fehler in Ihrem Ausgabefenster sehen
Ich bin nicht sicher, ob es eine bestimmte Entwurfsauswahl oder -beschränkung gibt, um die Eigenschaft nicht zu IsVisibleeiner Abhängigkeitseigenschaft zu machen , aber Sie können dies umgehen, indem Sie eine angehängte Eigenschaft erstellen. Diese Eigenschaft kann die CLR - Eigenschaft gebunden und setzt werden IsVisibleauf die , LayoutAnchorablewenn sie sich ändert.
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;
}
}
Sie können diese Eigenschaft in Ihrer XAML binden, aber wie gesagt, funktioniert dies nicht, da sie LayoutAnchorablenicht im visuellen Baum enthalten ist. Das gleiche Problem tritt bei DataGridSpalten auf. In diesem verwandten Beitrag finden Sie eine Problemumgehung für eine BindingProxyKlasse, die wir verwenden werden. Bitte kopieren Sie diese Klasse in Ihr Projekt.
Erstellen Sie eine Instanz des Bindungsproxys in Ihrem DockingManager.Resources. Es dient zum Zugriff auf den Menüpunkt.
<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>
Entfernen Sie Ihre alte IsVisibleBindung. Fügen Sie der angehängten Eigenschaft mithilfe von eine Bindung hinzu mnuPane1Proxy.
<xcad:LayoutAnchorable ContentId="content1"
x:Name="anchorable1"
IsSelected="True"
local:LayoutAnchorableProperties.IsVisible="{Binding Data.IsChecked, Source={StaticResource mnuPane1Proxy}}">
Setzen IsCheckedSie abschließend den Standardstatus in Ihrem Menüelement auf true, da dies der Standardstatus für ist IsVisibleund die Bindung bei der Initialisierung nicht aktualisiert InvalidOperationExceptionwird, da der Standardwert in den angehängten Eigenschaften festgelegt wird, der erforderlich ist, um zu verhindern, dass das Steuerelement ausgelöst wird ist nicht vollständig initialisiert.
<MenuItem Header="_Foo1" Name="mnuPane1" IsCheckable="True" IsChecked="True">