Laden...

[GELÖST] RelativeSource bei einen Style Binding?

Erstellt von Sebastian1989101 vor 8 Jahren Letzter Beitrag vor 8 Jahren 4.197 Views
Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 8 Jahren
[GELÖST] RelativeSource bei einen Style Binding?

Ich arbeite derzeit an einer Anwendung, welche mit dem MVVM-Entwurfsmuster erstellt wird. Soweit läuft auch alles rund, nur möchte der Kunde nun noch angepasste Kontextmenü-Elemente zur Auswahl haben. Und hier versteckt sich das Problem. Wie bekommt man ein Kontextmenü an einer GridView innerhalb einer ListView, wobei das Command im DataContext der Page steht? Aktuell sieht meine WPF-Implementierung für diese Page wie folgt aus:

<Page x:Class="CustomerOperatingSystem.View.Pages.FritzBox.FritzBoxStockStatusPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:model="clr-namespace:MyProject.Model.DataTransferObjects;assembly=CustomerOperatingSystem.Model"
      xmlns:viewModel="clr-namespace:MyProject.ViewModel.Pages.FritzBox;assembly=CustomerOperatingSystem.ViewModel">

    <Grid>
        <ListView Name="lvStock" Margin="5" AlternationCount="2" ItemsSource="{Binding FilteredEntries}">
            <ListView.Resources>
                <Style TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource {x:Type ListViewItem}}">
                    <Setter Property="ToolTip" Value="{Binding Note}" />

                    <Setter Property="ContextMenu">
                        <Setter.Value>
                            <ContextMenu>
                                <!-- Problem here -->
                                <MenuItem Header="Copy Serial" 
                                          Command="{Binding Path=DataContext.(viewModel:FritzBoxStockStatusPageModel.CopyInformationToClipboardCommand), 
                                                            RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Page}}}" 
                                          CommandParameter="{Binding SerialNumber}" />
                            </ContextMenu>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ListView.Resources>

            <ListView.View>
                <GridView>
                    <GridViewColumn Width="145">
                        <GridViewColumnHeader Click="SortClick" Tag="SerialNumber" Content="Seriennummer" />
                        <GridViewColumn.CellTemplate>
                            <DataTemplate DataType="{x:Type model:FritzBoxStockEntry}">
                                <TextBlock Text="{Binding SerialNumber}" HorizontalAlignment="Left" TextAlignment="Left" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                    <GridViewColumn Width="145">
                        <GridViewColumnHeader Click="SortClick" Tag="CwmpAccount" Content="CWMP-Account" />
                        <GridViewColumn.CellTemplate>
                            <DataTemplate DataType="{x:Type model:FritzBoxStockEntry}">
                                <TextBlock Text="{Binding CwmpAccount}" HorizontalAlignment="Left" TextAlignment="Left" />
                            </DataTemplate>
                        </GridViewColumn.CellTemplate>
                    </GridViewColumn>

                    <!-- Some more Columns here -->
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>
</Page>

Hierbei tritt zur Laufzeit folgender Fehler auf:

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='Finaltec.Presentation.Wpf.Windows.Page', AncestorLevel='1''. BindingExpression:Path=DataContext.(0); DataItem=null; target element is 'MenuItem' (Name=''); target property is 'Command' (type 'ICommand')

Ich hatte jetzt schon einige Dinge Probiert und bin so langsam ohne Idee, wie ich das ganze noch zum laufen bekommen könnte. Vielleicht hat hier jemand von euch eine Idee. Oder ist mein Ansatz für das Kontextmenü einfach komplett falsch und es müsste anderweitig implementiert werden? Laut Fehlermeldung kann er an der Stelle die RelativeSource nicht Auflösen. Muss hier noch etwas beachtet werden, wenn dies in einer Style-Definition erfolgt?

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog

2.080 Beiträge seit 2012
vor 8 Jahren

Das Problem ist, dass das ContextMenu nicht im Visual Tree liegt und daher auch nicht einfach sich am Tree hoch hangeln und deine RelativeSource suchen kann.

Ich hab ein paar Möglichkeiten ausprobiert, Eine komplizierter als die Andere.
Am angenehmsten fand ich bisher den BindingProxy.

Das ist im Prinzip eine Klasse, die eine DependencyProperty "Data" hat. Ein Objekt dieser Klasse landet irgendwo in den Resourcen weiter oben. Die Data-Property bindest Du an deinen DataContext. Da kannst Du regulär und wie bekannt binden.

Das BindingProxy könnte so aus sehen:

public class BindingProxy : Freezable
{
    public static readonly DependencyProperty DataProperty;

    static BindingProxy()
    {
        DataProperty = DependencyProperty.Register(nameof(Data), typeof(object), typeof(BindingProxy));
    }

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    protected override Freezable CreateInstanceCore()
    {
        return new BindingProxy();
    }
}

Nutzung in den Resourcen:

<local:BindingProxy x:Key="DataContextBindingProxy" Data={Binding} />

Im ContextMenu gibst Du im Binding für die Source-Property eine StaticResource an, die auf deinen BindingProxy zeigt:

{Binding Data.MyCoolViewModelProperty, Source={StaticResource DataContextBindingProxy}}

Das Code ist ungetestet im Browser geschrieben, also verzeih mir bitte Fehler 😄

NuGet Packages im Code auslesen
lock Alternative für async/await

Beim CleanCode zählen nicht die Regeln, sondern dass wir uns mit diesen Regeln befassen, selbst wenn wir sie nicht befolgen - hoffentlich nach reiflichen Überlegungen.

5.299 Beiträge seit 2008
vor 8 Jahren

ich hab hier ein TreeviewItem-ContextMenü, dessen Command wird im Viewmodel verarbeitet, und übergibt als CommandParameter den angeklicksten TreeviewItem-DataContext:


  <TreeView x:Name="tv" hlp:GridEx.Range="1" ItemsSource="{Binding Nodes}" hlp:VisualSubscribe.Subscribe="{Binding _TvSubscribe}" >
    <ItemsControl.ItemTemplate>
      <HierarchicalDataTemplate ItemsSource="{Binding}"  >
        <Border x:Name="brd" BorderBrush="{Binding MappingState, Converter={StaticResource brshConnected}, Mode=OneWay}" BorderThickness="6,0,0,0" Padding="1,0,0,0"   >
          <FrameworkElement.ContextMenu>
            <ContextMenu hlp:SelectOnOpen.AttachedValue="true"  ItemsSource="{Binding MenuItems}">
              <ItemsControl.ItemContainerStyle>
                <Style TargetType="MenuItem">
                  <Style.Setters>
                    <Setter Property="Header" Value="{Binding Name}"/>
                    <Setter Property="Command" Value="{Binding Command}"/>
                    <Setter Property="CommandParameter" Value="{Binding Path=PlacementTarget.DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}}"/>
                  </Style.Setters>
                </Style>
              </ItemsControl.ItemContainerStyle>
            </ContextMenu>
          </FrameworkElement.ContextMenu>
          <TextBlock Text="{Binding Name}"/>
        </Border>
      </HierarchicalDataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemContainerStyle>
      <Style TargetType="TreeViewItem">
        <Setter Property="IsExpanded" Value="true"/>
        <Setter Property="IsSelected" Value="{Binding IsSelected}"/>
      </Style>
    </ItemsControl.ItemContainerStyle>
  </TreeView>

Also der Trick ist, dass das MenuItem sein ContextMenu sucht, und von dem sein PlacementTarget der DataContext...
Wird hier als CommandParameter genutzt, aber den Trick kann man sicher auch zu anderem nutzen.

Der frühe Apfel fängt den Wurm.

Sebastian1989101 Themenstarter:in
241 Beiträge seit 2010
vor 8 Jahren

Mit dem BindingProxy hat das ganze wunderbar geklappt, vielen Dank. 😃

WAGO Kontakttechnik GmbH & Co. KG / Software Notion
Softwareentwicklung

C# .NET with WPF, ASP, Xamarin and Unity
Personal Blog: Development Blog