【问题标题】:How do I bind to a property on the ViewModel from inside a DataTemplate?如何从 DataTemplate 中绑定到 ViewModel 上的属性?
【发布时间】:2019-09-10 19:40:27
【问题描述】:

我无法将 MenuFlyoutItem 的命令绑定到我的 ViewModel 上的 RelayCommand。我已经尝试了所有我能想到的方法,ElementName、RelativeSource 等。谁能告诉我我做错了什么?下面代码中显示的其他两个绑定有效。只有命令绑定没有。我的意思是我在由 RelayCommand 调用的 OnFilterListCommand 方法中设置了一个断点。当我单击菜单弹出项时,执行永远不会到达该断点。

<wct:DataGridComboBoxColumn.HeaderStyle>
    <Style TargetType="controlsprimitives:DataGridColumnHeader">
        <Setter Property="ContentTemplate"> 
            <Setter.Value>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
                        <TextBlock Text="Company" TextWrapping="Wrap"/>
                        <Button HorizontalAlignment="Right" x:Name="MyButton" Content="test">
                            <i:Interaction.Behaviors>                                                  
                                <core:EventTriggerBehavior EventName="Click">
                                <core:InvokeCommandAction Command="{Binding ElementName=thePage, Path=DataContext.OpenFlyoutCommand}" CommandParameter="{Binding ElementName=MyButton}"/>                                                            
                                 </core:EventTriggerBehavior>                                                       
                            </i:Interaction.Behaviors>
                            <FlyoutBase.AttachedFlyout>
                                <Flyout helpers:BindableFlyout.ItemsSource="{Binding ElementName=theView, Path=DataContext.SourceForCompaniesList}" x:Name="theFlyout">                                                              
                                    <helpers:BindableFlyout.ItemTemplate>                                                               
                                        <DataTemplate>
                                            <MenuFlyoutItem Text="{Binding CompanyName}" Command="{Binding Path=DataContext.FilterListCommand, ElementName=theView}" IsTapEnabled="True"/>                                                                    
                                        </DataTemplate>                                                     
                                    </helpers:BindableFlyout.ItemTempla
                                </Flyout>
                            </FlyoutBase.AttachedFlyout>

这是来自 ViewModel 的适用代码。

private RelayCommand<object> _filterListCommand;
public RelayCommand<object> FilterListCommand => _filterListCommand
                ?? (_filterListCommand = new RelayCommand<object>(OnFilterListCommand));

private void OnFilterListCommand(object obj)
{
    string selectedCompany = obj as string;
    ...
}

我正在使用 Jerry Nixon 的解决方案将 ItemsSources 属性添加到 FlyoutMenu:

public class BindableFlyout : DependencyObject
{
    #region ItemsSource

    public static IEnumerable GetItemsSource(DependencyObject obj)
    {

        return obj.GetValue(ItemsSourceProperty) as IEnumerable;


    }
    public static void SetItemsSource(DependencyObject obj, IEnumerable value)
    {

        obj.SetValue(ItemsSourceProperty, value);

    }
    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.RegisterAttached("ItemsSource", typeof(IEnumerable),
        typeof(BindableFlyout), new PropertyMetadata(null, ItemsSourceChanged));
    private static void ItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    { Setup(d as Windows.UI.Xaml.Controls.Flyout); }

    #endregion

    #region ItemTemplate

    public static DataTemplate GetItemTemplate(DependencyObject obj)
    {
        return (DataTemplate)obj.GetValue(ItemTemplateProperty);
    }
    public static void SetItemTemplate(DependencyObject obj, DataTemplate value)
    {
        obj.SetValue(ItemTemplateProperty, value);
    }
    public static readonly DependencyProperty ItemTemplateProperty =
        DependencyProperty.RegisterAttached("ItemTemplate", typeof(DataTemplate),
        typeof(BindableFlyout), new PropertyMetadata(null, ItemsTemplateChanged));
    private static void ItemsTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    { Setup(d as Windows.UI.Xaml.Controls.Flyout); }

    #endregion

    private static async void Setup(Windows.UI.Xaml.Controls.Flyout m)
    {
        if (Windows.ApplicationModel.DesignMode.DesignModeEnabled)
            return;
        var s = GetItemsSource(m);
        if (s == null)
            return;
        var t = GetItemTemplate(m);
        if (t == null)
            return;
        var c = new Windows.UI.Xaml.Controls.ItemsControl
        {
            ItemsSource = s,
            ItemTemplate = t,
        };
        var n = Windows.UI.Core.CoreDispatcherPriority.Normal;
        Windows.UI.Core.DispatchedHandler h = () => m.Content = c;
        await m.Dispatcher.RunAsync(n, h);
    }
}

我在这里找到了:http://blog.jerrynixon.com/2013/12/xaml-how-to-add-itemssource-to-windows.html

【问题讨论】:

  • 当我使用命令绑定时,它运行良好。你能展示更多关于 RelayCommand 类的代码吗?
  • 谢谢费旺。我从我试图绑定的 RelayCommand 添加了代码。我开始怀疑 BindableFlyout 类是否有一些我不理解的地方。我也添加了。
  • 从您的代码中,您的 FilterListCommand 和 SourceForCompaniesList 在同一个视图模型中吗?您应该将 FilterListCommand 和 CompanyName 放入同一模型中。另外,能否提供一个样本让我们复现这个问题?
  • 是的,SourceForCompaniesList 和 FilterListCommand 都在同一个 ViewModel 中。顺便说一句,我正在尝试做的是在gridview的列标题中放置一个下拉列表,以便用户可以像在Excel中一样过滤列。

标签: c# xaml uwp mvvm-light


【解决方案1】:

这是 Windows 10 中可用的最直接数据绑定的示例(x:Bind 使用 编译绑定,它在编译时就已经发现错误并提高了性能) :

1) XAML 页面:

<ListView ItemsSource="{x:Bind ViewModel.ItemsList, Mode=OneWay}">
    <ListView.ItemTemplate>
        <DataTemplate x:DataType="SampleObjectClass">
            <TextBlock Text="{x:Bind Title}"/>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

2) 模型类:

public class SampleObjectClass
{
    public String Title { get; set; }
}

3) MainPage.xaml.cs:

public sealed partial class MainPage : Page
{
    public AppViewModel ViewModel { get; set; } = new AppViewModel();
    public static MainPage Current;
    // The "Current" property is necessary in order to retriew the singleton istance of the AppViewModel in the entire app... thus we are using the MainPage as a "shell" structure for the app


    public MainPage()
    {
        Current = this;

        // Other things...
    }

    // Other stuff...
}

4) AppViewModel 类:

public class AppViewModel : BaseBind
{
    public AppViewModel()
    {
        ItemsList = new ObservableCollection<ItemsList >();
        ItemsList .CollectionChanged += (sender, e) =>
        {
            // Do something if the list changes (e.g. update another property in the viewmodel class)...
        };
    }

    public ObservableCollection<SampleObjectClass> ItemsList { get; set; }
}

编辑:当然,BaseBind 类是实现 INotifyPropertyChanged 接口。

【讨论】:

  • 谢谢。我实际上已经尝试过 x:bind 但得到“不能在样式中使用 x:bind”错误。我添加了更多代码来显示样式。
【解决方案2】:

我对MenuFlyoutItem一无所知,但通常一个控件只需要以下内容:

    <FlyoutBase.AttachedFlyout>
     <Flyout helpers:BindableFlyout.ItemsSource="{Binding ElementName=thePage, Path=DataContext.SourceForCompaniesList}" x:Name="theFlyout">                                                              
        <helpers:BindableFlyout.ItemTemplate>                                                               
            <DataTemplate>
                <MenuFlyoutItem Text="{Binding CompanyName}" Command="{Binding FilterListCommand}" IsTapEnabled="True"/>                                                                    
            </DataTemplate>                                                     
        </helpers:BindableFlyout.ItemTemplate>
     </Flyout>
</FlyoutBase.AttachedFlyout>

“thePage”是您的视图或视图模型的名称吗?显然,如果它不是您的视图模型,那么这将不起作用,如果是,则在您的 xaml 文件或后面的代码中将其绑定到您的数据上下文会更容易。

更新:

我想我明白你在做什么。有多种方法可以将参数传递给命令,但如果我按照这里的想法,最简单的方法就是调用命令并在视图模型中使用绑定属性 CompanyName。

(这里继续记忆,如有错误会编辑)。

public RelayCommand FilterListCommand;

FilterListCommand = new RelayCommand(() =>
{
    string selectedCompany = obj as string;  
    MessageBox.Show(selectedCompany); 
});

尝试一下,确保 CompanyName 是您的视图模型中正确定义的公共属性,该属性会引发属性更改。

公平地说,我发现了一些与您的原始示例类似的人员绑定示例,这可能是该平台常见的新习语。只是想在这里使用我在 Silverlight/WPF 方面的经验。

【讨论】:

  • 谢谢。 “thePage”是视图。我应该这样称呼它。我认为这可以工作,因为视图的数据上下文设置为视图模型。奇怪的是 ItemsSource 绑定和 CompanyName 绑定一样工作正常。只有命令绑定不起作用。
  • 不知道如何解释。只要视图的数据上下文连接到视图模型,我会尽可能删除所有额外的数据上下文规范。只要您在虚拟机中正确设置了中继命令,它就应该可以工作。
  • 再次感谢。我也会从视图模型中添加代码。
  • 谢谢。我实际上无法执行命令。我的意思是我在 OnFilterListCommand 方法中设置了一个断点,当我单击 menuflyoutitem 时执行永远不会到达那里。我在想要么必须是事件没有被触发,要么它没有在视图模型上找到命令。我实际上尝试完全删除命令参数,它仍然不起作用。
【解决方案3】:

我终于明白了。要从数据模板中绑定到视图模型上的命令,我必须使用以下内容:

<MenuFlyoutItem Text="{Binding CompanyName}" 
                Command="{Binding CompaniesListViewModel.CompanyListCommand, Source={StaticResource Locator}}" 
                CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=Text}" />

Locator 指的是 ViewmodelLocator。 (注意:我使用的是 MVVMLight。)

【讨论】:

    【解决方案4】:

    我稍微修改了BindableFlyout类,所以它支持DataTempalteSelector

    1. 添加Margin(-14, -8, -14, -8),让它看起来更像MenuFlyout
    2. 重复使用ItemsControl,这样在更改值时效率会更高。
    3. flyout 分配给Tag,否则无法在子事件处理程序中找到弹出窗口。使用var flyout = (Flyout)((((DependencyObject)sender).GetParents().FirstOrDefault(obj =&gt; obj is ItemsControl)).Tag);
    internal static class DependencyObjectExtension
    {
        internal static IEnumerable<DependencyObject> GetParents(this DependencyObject child)
        {
            var parent = VisualTreeHelper.GetParent(child);
            while (parent != null)
            {
                yield return parent;
                child = parent;
                parent = VisualTreeHelper.GetParent(child);
            }
        }
    }
    
    public class BindableFlyout : DependencyObject
    {
        #region ItemsSource
    
        public static readonly DependencyProperty ItemsSourceProperty =
            DependencyProperty.RegisterAttached("ItemsSource", typeof(IEnumerable), typeof(BindableFlyout), new PropertyMetadata(null, OnDependencyPropertyChanged));
    
        public static IEnumerable GetItemsSource(DependencyObject obj) =>
            obj.GetValue(ItemsSourceProperty) as IEnumerable;
        public static void SetItemsSource(DependencyObject obj, IEnumerable value) =>
            obj.SetValue(ItemsSourceProperty, value);
    
        #endregion
    
        #region ItemTemplate
    
        public static readonly DependencyProperty ItemTemplateProperty =
            DependencyProperty.RegisterAttached("ItemTemplate", typeof(DataTemplate), typeof(BindableFlyout), new PropertyMetadata(null, OnDependencyPropertyChanged));
    
        public static DataTemplate GetItemTemplate(Flyout obj) =>
            (DataTemplate)obj.GetValue(ItemTemplateProperty);
        public static void SetItemTemplate(Flyout obj, DataTemplate value) =>
            obj.SetValue(ItemTemplateProperty, value);
    
        #endregion
    
        #region ItemTemplateSelector
    
        public static readonly DependencyProperty ItemTemplateSelectorProperty =
            DependencyProperty.RegisterAttached("ItemTemplateSelector", typeof(DataTemplateSelector), typeof(BindableFlyout), new PropertyMetadata(null, OnDependencyPropertyChanged));
    
        public static DataTemplateSelector GetItemTemplateSelector(Flyout obj) =>
            (DataTemplateSelector)obj.GetValue(ItemTemplateSelectorProperty);
        public static void SetItemTemplateSelector(Flyout obj, DataTemplate value) =>
            obj.SetValue(ItemTemplateSelectorProperty, value);
    
        #endregion
    
        private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var flyout = (Flyout)d;
            var items = flyout.Content as ItemsControl;
            if (items == null)
                items = new ItemsControl() { Margin = new Thickness(-14, -8, -14, -8) };
            void callback()
            {
                flyout.Content = items;
                items.ItemsSource = GetItemsSource(flyout);
                items.ItemTemplate = GetItemTemplate(flyout);
                items.ItemTemplateSelector = GetItemTemplateSelector(flyout);
                items.Tag = flyout;
            }
            var dispatcher = Application.Current.Dispatcher;
            if (dispatcher.CheckAccess())
            {
                callback();
                return;
            }
            dispatcher.Invoke(callback, DispatcherPriority.Normal);
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-11
      • 2021-05-21
      • 2010-11-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-05
      相关资源
      最近更新 更多