Karmeye
Karmeye

Reputation: 1535

How to switch `ContextFlyout` on `DataTemplate` based on properties of the bound type?

Let’s say I have a ListView, a DataTemplate for the items and two MenuFlyouts:

<Page>
    <Page.Resources>
        <MenuFlyout x:Key="Menu1" />
        <MenuFlyout x:Key="Menu2" />
        <DataTemplate x:Key="Template"
                      x:DataType="local:MyItem">
            <TreeViewItem>
                <Grid ContextFlyout="{StaticResource Menu1}">
                </Grid>
            </TreeViewItem>
        </DataTemplate>
    <TreeViewItem
    </Page.Resources>
    <ListView ItemTemplate="{StaticResource Template}"
              Items="{x:Bind ...}"
</Page>

So each item in the ListView has a context/right-click menu set to Menu1.

Now, let's say I want to have a different context menu for some of the items in the ListView, e.g., Menu2. How can I accomplish this?

In other words, how do I switch the ContextFlyout to MenuFlyout Menu2 for certain MyItem items in the ListView based on values of properties in the MyItem type?

Update, Clarification

I wasn't specifying the question completely. Obviously, to accomplish that you would bind the to the ContextFlyout. This was not my question.

In my case I cannot do that because:

So I need to find a way in the UI layer to hook in to each item provided by the view model and then set the ContextMenu based on the properties of that item.

The question becomes: how to, in the UI layer (e.g., a Page or its code behind), change a value (which is not bound to the view model, it's a resource in the UI layer) within a DataTemplate, based on the current bound item?

I've also looked at ContextRequested but the docs recommend setting the ContextFlyout property instead.

I hope this makes my question clearer.

Possible Solution

The only way I can think of how to do it is in a DataTemplateSelector:

<MenuFlyout x:Key="MenuFlyout1">
    <MenuFlyoutItem Command="{x:Bind ViewModel.Command1}"
                    CommandParameter="{Binding}"
                    Text="1">
    </MenuFlyoutItem>
</MenuFlyout>

<MenuFlyout x:Key="MenuFlyout2">
    <MenuFlyoutItem Command="{x:Bind ViewModel.Command2}"
                    CommandParameter="{Binding}"
                    Text="2">
    </MenuFlyoutItem>
</MenuFlyout>

<DataTemplate x:Key="MyDataTemplate"
              x:DataType="local:MyItem">
    <TreeViewItem />
</DataTemplate>

<local:MyTemplateSelector x:Key="Selector"
                          MenuFlyout1="{StaticResource MenuFlyout1}"
                          MenuFlyout2="{StaticResource MenuFlyout2}"
                          MyDataTemplate="{StaticResource MyDataTemplate}" />
public class MyTemplateSelector : DataTemplateSelector
{
    public MyDataTemplate Template { get; set; } = null!;
    public MenuFlyout MenuFlyout1 { get; set; } = null!;
    public MenuFlyout MenuFlyout2 { get; set; } = null!;

    protected override DataTemplate? SelectTemplateCore(object item)
    {
        MenuFlyout menuToUse;
        if (item.Type == 1)
            menu = MenuFlyout1;        
        else 
            menu = MenuFlyout2;   
     
        // How to assign menu to `Template` ?

        return Template;
    }
}

But I cannot manage to assign the menu to the Template. It've tried

var treeViewItem = Template.GetElement(new());
treeViewItem.ContextFlyout = MenuFlyout1;

but it has no effect.

Upvotes: 0

Views: 225

Answers (1)

YangXiaoPo-MSFT
YangXiaoPo-MSFT

Reputation: 2130

I can make Resource dictionaries with {x:Bind} work and cannot override the resource key but you can get a workaround from https://learn.microsoft.com/en-us/answers/questions/1465387/generic-xaml-x-bind-with-converter-binding.

TemplatesResourceDictionary.xaml

<?xml version="1.0" encoding="utf-8"?>
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="App1.TemplatesResourceDictionary"
    xmlns:local="using:App1">

    <DataTemplate x:Key="EmployeeTemplate" x:DataType="local:IEmployee">
        <Grid Background="{x:Bind Color}">
            <TextBlock Text="{x:Bind Name}" Foreground="{StaticResource MyBrush}"/>
        </Grid>
    </DataTemplate>
</ResourceDictionary>

TemplatesResourceDictionary.xaml.cs

namespace App1
{
    public partial class TemplatesResourceDictionary
    {
        public TemplatesResourceDictionary()
        {
            this.InitializeComponent();
        }
    }
}

IEmployee

class IEmployee
{
    public IEmployee(string n, Brush b)
    {
        Name = n;
        Color = b;
    }
    public string Name;
    public Brush Color;
}

App.xaml

<?xml version="1.0" encoding="utf-8"?>
<Application
    x:Class="App1.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1">
    <Application.Resources>
        <ResourceDictionary>
            <SolidColorBrush x:Key="MyBrush" Color="Red" />
            
            <ResourceDictionary.MergedDictionaries>
                <XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
                <!-- Other merged dictionaries here -->
                <local:TemplatesResourceDictionary/>
            </ResourceDictionary.MergedDictionaries>
            <!-- Other app resources here -->
        </ResourceDictionary>
    </Application.Resources>
</Application>

MainWindow.xaml

<StackPanel>
    <StackPanel.Resources>
        <Color x:Key="MyBrush">Black</Color>
    </StackPanel.Resources>
    <ListView ItemsSource="{x:Bind Employees}" ItemTemplate ="{StaticResource EmployeeTemplate}">
        <ListView.Resources>
            <Color x:Key="MyBrush">Green</Color>
        </ListView.Resources>
    </ListView>

    <ListView ItemsSource="{x:Bind Employees}" ItemTemplate ="{StaticResource EmployeeTemplate}">
    </ListView>
</StackPanel>

enter image description here

Upvotes: 1

Related Questions