XAML “Interaction.Behaviors”应该在WinUI 3的数据模板对象中工作吗?

ne5o7dgx  于 2022-12-07  发布在  其他
关注(0)|答案(1)|浏览(146)

On Uno Platform project, using several data templates for TabViewItem , TreeViewItem and ListViewItem , trying to fire commands via various events, like ItemInvoked (TreeView), DoubleTapped (ListView), CloseRequested (TabView).
Strangely enough it works for few invocations, then it stops. Note that (also the same) commands bound to Buttons via their Command binding continue working.
Example of TabView close attempt. Typically works for first 3 to 5 tabs:

<DataTemplate x:Key="HtmlFileTemplate" x:DataType="local:FileContentViewModel">
            <TabViewItem Header="{x:Bind Info.Name}">
                <StackPanel Orientation="Vertical">
                    <TextBlock Text="{x:Bind Content}" />
                    <!--
                        This just works:
                    -->
                    <Button Command="{x:Bind CloseCommand}">Invoke FileContentViewModel.CloseCommand</Button>
                </StackPanel>
                <!-- 
                    This stops working after few invocations
                    (typically together with all other Interaction.Behaviors bindings):
                -->
                <i:Interaction.Behaviors>
                    <ic:EventTriggerBehavior EventName="CloseRequested">
                        <ic:InvokeCommandAction Command="{x:Bind CloseCommand}" />
                    </ic:EventTriggerBehavior>
                </i:Interaction.Behaviors>
            </TabViewItem>
        </DataTemplate>

        <local:TabsTemplateSelector x:Key="TabsTemplateSelector" HtmlFileTemplate="{StaticResource HtmlFileTemplate}" ... />
    ...

    <TabView TabItemsSource="{x:Bind ViewModel.Tabs}" TabItemTemplateSelector="{StaticResource TabsTemplateSelector}">
    </TabView>

I hope, there is some flagrant issue in my usage I can't simply see. Any Help appreciated. Using latest uno stuff (WinUI, dotnet6), debugging on Windows head:

  • dotnet new unoapp -o UnoWinUI3AppName
  • <PackageReference Include="CommunityToolkit.Mvvm" Version="7.1.2" />
  • <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.1.3" />
  • <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.22621.1" />
  • <PackageReference Include="Uno.Microsoft.Xaml.Behaviors.Interactivity.WinUI" Version="2.3.0" />
  • <PackageReference Include="Uno.Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.3.0" />

Still trying to find minimal sample exhibiting the mentioned issues.
Also tried to check the XAML generated code, but it is too much... well... generated :-(

EDIT:

"Simplified" the use case to following:

  • Have TabView on a page.
  • First tab contains ListView with items.
  • Other tabs contain "opened" items (dummy record).
  • Item opens on double-click ( DoubleTapped ) in ListView on first page.
  • Item closes on X click on a tab (in TabView ).
  • Both TabView and ListView use DataTemplate s.
  • Separate UI from code as much as possible (binding, commands, VMs, etc.).
  • Based on default WinUI "Hello World" app template.
  • Project files
    EDIT 2: For anyone interested, based on Andrew KeepCoding's hints and linked issue answers, I mixed code behind with custom attaching of the behaviors to the templated items via new attached property:
public static class InteractionEx
{
    public static readonly DependencyProperty AttachBehaviorsProperty = DependencyProperty.RegisterAttached
    (
        "AttachBehaviors",
        typeof(object),
        typeof(FrameworkElement),
        new PropertyMetadata(false, AttachBehaviorsChanged)
    );

    public static object GetAttachBehaviors(DependencyObject o) => o.GetValue(AttachBehaviorsProperty);

    public static void SetAttachBehaviors(DependencyObject o, object value) => o.SetValue(AttachBehaviorsProperty, value);

    private static void AttachBehaviorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var behaviors = e.NewValue switch
        {
            Behavior single => new BehaviorCollection { single },
            BehaviorCollection multiple => multiple,
            _ => null,
        };

        Interaction.SetBehaviors(d, behaviors);
    }
}

In XAML the behaviors then can be attached to a control like:

<ListView ItemsSource="{x:Bind Items}" ItemTemplateSelector="{StaticResource ListItemTemplateSelector}" SelectedItem="{x:Bind SelectedItem, Mode=TwoWay}">
    <local:InteractionEx.AttachBehaviors>
        <interactivity:BehaviorCollection>
            <core:EventTriggerBehavior EventName="DoubleTapped">
                <core:InvokeCommandAction Command="{x:Bind OpenCommand}" />
                <core:CallMethodAction MethodName="Open" TargetObject="{x:Bind}" />
            </core:EventTriggerBehavior>
        </interactivity:BehaviorCollection>
    </local:InteractionEx.AttachBehaviors>
</ListView>
ryevplcw

ryevplcw1#

我想您会发现这些answers很有帮助。
但是让我建议另一个选择。我花了一些时间在你的repro项目上,在我看来,我认为如果你放弃Interaction.Behaviors.代码隐藏如果它是UI相关的,没有业务逻辑,它会更干净和可读。

    • 主窗口. xaml**
<Window
    x:Class="TabViewTest.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:helpers="using:TabViewTest.Helpers"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewmodels="using:TabViewTest.ViewModels"
    mc:Ignorable="d">

    <Grid>
        <Grid.Resources>
            <DataTemplate x:Key="BrowserTemplate" x:DataType="viewmodels:BrowserTabViewModel">
                <TabViewItem Header="Browser">
                    <ListView x:Name="ItemList" ItemsSource="{x:Bind Items}">
                        <ListView.ItemTemplate>
                            <DataTemplate x:DataType="viewmodels:ItemViewModel">
                                <TextBlock DoubleTapped="TextBlock_DoubleTapped" Text="{x:Bind Id}" />
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </TabViewItem>
            </DataTemplate>

            <DataTemplate x:Key="ContentTemplate" x:DataType="viewmodels:ContentTabViewModel">
                <TabViewItem Header="{x:Bind ItemViewModel.Id}">
                    <TextBlock Text="{x:Bind ItemViewModel.Id}" />
                </TabViewItem>
            </DataTemplate>

            <helpers:TabItemTemplateSelector
                x:Key="TabTemplateSelector"
                BrowserTemplate="{StaticResource BrowserTemplate}"
                ContentTemplate="{StaticResource ContentTemplate}" />
        </Grid.Resources>

        <TabView
            TabCloseRequested="TabView_TabCloseRequested"
            TabItemTemplateSelector="{StaticResource TabTemplateSelector}"
            TabItemsSource="{x:Bind ViewModel.Tabs}" />
    </Grid>

</Window>
    • 主窗口. xaml. cs**
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
using System.Linq;

namespace TabViewTest.ViewModels;

[ObservableObject]
public partial class MainWindowViewModel
{
    [ObservableProperty]
    private ObservableCollection<TabViewModel> tabs = new();

    private readonly BrowserTabViewModel browserTabViewModel;

    public MainWindowViewModel()
    {
        browserTabViewModel = new();
        Tabs.Add(browserTabViewModel);
    }

    [RelayCommand]
    private void NewTabRequest(ItemViewModel itemViewModel)
    {
        Tabs.Add(new ContentTabViewModel(itemViewModel));
    }

    [RelayCommand]
    private void CloseTabRequest(ItemViewModel itemViewModel)
    {
        if (Tabs
            .OfType<ContentTabViewModel>()
            .Where(x => x.ItemViewModel == itemViewModel)
            .FirstOrDefault() is ContentTabViewModel target)
        {
            Tabs.Remove(target);
        }
    }
}

相关问题