J'essaye de lier AvalonDock LayoutAnchorables à leurs éléments de menu respectifs dans WPF. Si coché dans le menu, l'ancrage doit être visible. Si elle n'est pas cochée dans le menu, l'ancrage doit être masqué.

Les deux IsChecked et IsVisible sont booléens donc je ne m'attendrais pas à ce qu'un convertisseur soit nécessaire. Je peux définir la propriété LayoutAnchorable IsVisible sur True ou False, et le comportement est comme prévu dans la vue de conception.

Cependant, si j'essaie d'implémenter la liaison comme ci-dessous, j'obtiens l'erreur

'Binding' ne peut pas être défini sur la propriété 'IsVisible' de type 'LayoutAnchorable'. Un 'Binding' ne peut être défini que sur un DependencyProperty d'un DependencyObject.

Le problème est ici:

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

Comment puis-je faire ceci?

<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>

Mise à jour:

Ma mise en œuvre de la réponse de BionicCode. Mon problème restant est que si je ferme un volet, l'élément de menu reste coché.

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 derrière

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;
    }
}
1
wotnot 29 août 2020 à 11:52

2 réponses

Meilleure réponse

Les éléments de disposition XAML AvalonDock ne sont ni des contrôles ni des dérivés de UIElement. Ils servent de modèles simples (bien qu'ils s'étendent DependencyObject).
Les propriétés de LayoutAnchorable ne sont pas implémentées en tant que DependencyProperty, mais implémentent plutôt INotifyPropertyChanged (comme dit précédemment, les éléments de mise en page servent de modèle de vue du contrôle). Par conséquent, ils ne prennent pas en charge les enchères de données (en tant que cible de liaison).

Chacun de ces éléments de mise en page XAML a un contrôle correspondant qui sera en fait rendu avec l'élément de mise en page sous la forme DataContext. Les noms correspondent au nom de l'élément de mise en page avec le suffixe Control attaché. Si vous souhaitez connecter ces contrôles ou conteneurs d'éléments, par exemple LayoutAnchorableItem à votre modèle de vue, vous devez créer un Style qui cible ce conteneur. La faille suivante est que le DataContext de ces conteneurs n'est pas votre modèle de données que le contrôle est destiné à afficher, mais le modèle interne du contrôle. Pour accéder à votre modèle de vue, vous devez accéder par ex. LayoutAnchorableControl.LayoutItem.Model (car le LayoutAnchorableControl.DataContext est le LayoutAnchorable).

Les auteurs se sont évidemment perdus en étant trop désireux d'implémenter le contrôle lui-même à l'aide de MVVM (comme indiqué dans leur documentation) et oublient de cibler l'application client MVVM . Ils ont cassé le modèle WPF commun. Ça a l'air bien à l'extérieur, mais pas si bon à l'intérieur.

Pour résoudre votre problème, vous devez introduire une propriété de dépendance intermédiaire sur votre vue. Une propriété enregistrée a changé de rappel déléguerait alors la visibilité pour basculer la visibilité de l'ancrable.
Il est également important de noter que les auteurs d'AvalonDock n'ont pas utilisé le UIElement.Visibility pour gérer la visibilité. Ils ont introduit une logique de visibilité personnalisée indépendante de la propriété du framework.

Comme mentionné précédemment, il existe toujours une approche basée sur un modèle pur, où vous mettez en page la vue initiale en fournissant une implémentation ILayoutUpdateStrategy. Vous définissez ensuite des styles pour câbler des modèles de vue et de vue. Le codage en dur de la vue à l'aide des éléments de disposition XAML entraîne certains inconvénients dans les scénarios plus avancés.

LayoutAnchorable expose une méthode Show() et Close() ou la propriété IsVisible pour gérer la visibilité. Vous pouvez également vous lier à une commande lors de l'accès à LayoutAnchorableControl.LayoutItem (par exemple à partir d'un ControlTemplate), qui renvoie un LayoutAnchorableItem. Ceci LayoutAnchorableItem expose un 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
BionicCode 1 sept. 2020 à 10:20

Il y a deux problèmes majeurs avec vos liaisons.

  1. La propriété IsVisible n'est pas une DependencyProperty, mais juste une propriété CLR, vous ne pouvez donc pas la lier
  2. Un LayoutAnochorable ne fait pas partie de l'arborescence visuelle, donc les liaisons ElementName et RelativeSource ne fonctionnent pas, vous verrez les erreurs de liaison correspondantes dans votre fenêtre de sortie

Je ne sais pas s'il existe un choix de conception spécifique ou une limitation pour ne pas faire de la propriété IsVisible une propriété de dépendance, mais vous pouvez contourner ce problème en créant une propriété jointe. Cette propriété peut être liée et définit la propriété CLR IsVisible sur le LayoutAnchorable lorsqu'elle change.

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;
   }
}

Vous pouvez lier cette propriété dans votre XAML, mais comme cela a été dit, cela ne fonctionnera pas, car le LayoutAnchorable n'est pas dans l'arborescence visuelle. Le même problème se produit pour les colonnes DataGrid. Dans cet article connexe, vous trouvez une solution de contournement avec une classe BindingProxy que nous utiliserons. Veuillez copier cette classe dans votre projet.

Créez une instance du proxy de liaison dans votre DockingManager.Resources. Il sert à accéder à l'élément de menu.

<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>

Supprimez votre ancienne liaison IsVisible. Ajoutez une liaison à la propriété jointe à l'aide de mnuPane1Proxy.

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

Enfin, définissez l'état par défaut IsChecked dans votre élément de menu sur true, car il s'agit de l'état par défaut pour IsVisible et la liaison n'est pas mise à jour lors de l'initialisation en raison de la définition de la valeur par défaut dans le les propriétés attachées, qui sont nécessaires pour empêcher le InvalidOperationException qui est levé car le contrôle n'est pas complètement initialisé.

<MenuItem Header="_Foo1" Name="mnuPane1" IsCheckable="True" IsChecked="True">
1
thatguy 29 août 2020 à 11:53