【问题标题】:WPF DataTemplate binding parameter in Window.ResourcesWindow.Resources 中的 WPF DataTemplate 绑定参数
【发布时间】:2017-08-17 18:45:38
【问题描述】:

我正在创建一个数据网格,在列标题中有过滤器。它有效,但我认为这不是一个好方法。给你看代码,很简单的例子:

观点

<Window x:Class="TestDataGridApp.Views.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:viewModels="clr-namespace:TestDataGridApp.ViewModels"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="300">
    <Window.DataContext>
        <viewModels:MainWindowViewModel />
    </Window.DataContext>
    <Window.Resources>
        <DataTemplate x:Key="DataGridHeader">
            <DockPanel>
                <TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
                <TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterName, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
            </DockPanel>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <DataGrid ItemsSource="{Binding ItemCollection}" AutoGenerateColumns="False">
            <DataGrid.ColumnHeaderStyle>
                <Style TargetType="{x:Type DataGridColumnHeader}">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                </Style>
            </DataGrid.ColumnHeaderStyle>

            <DataGrid.Columns>
                <DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
                <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

视图模型

namespace TestDataGridApp.ViewModels
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Windows.Data;
    using TestDataGridApp.Entities;
    using Prism.Mvvm;
    public class MainWindowViewModel : BindableBase
    {
        private string _filterId;
        private string _filterName;
        private ObservableCollection<Item> _items = new ObservableCollection<Item>();

        public MainWindowViewModel()
        {
            for (int i = 1; i <= 100; ++i)
            {
                Items.Add(new Item() {Id = i, Name = $"Item{i}"});
            }
        }
        public string FilterId
        {
            get { return _filterId; }
            set
            {
                SetProperty(ref _filterId, value);
                TriggerFilters();
            }
        }
        public string FilterName
        {
            get { return _filterName; }
            set
            {
                SetProperty(ref _filterName, value);
                TriggerFilters();
            }
        }
        public ObservableCollection<Item> Items
        {
            get { return _items; }
            set { SetProperty(ref _items, value); }
        }
        public ICollectionView ItemCollection => CollectionViewSource.GetDefaultView(Items);

        private void TriggerFilters()
        {
            ItemCollection.Filter = o => FilterItem((Item)o);
        }
        private bool FilterItem(Item item)
        {
            try
            {
                bool checkId = false;
                bool checkName = false;

                int itemId = 0;
                if (!string.IsNullOrEmpty(FilterId) && int.TryParse(FilterId, out itemId)) checkId = true;
                if (!string.IsNullOrEmpty(FilterName)) checkName = true;

                if (!checkId && !checkName) return true;
                if (item == null) return false;

                bool checkIdIsOk = (checkId && item.Id == int.Parse(FilterId) || !checkId);
                bool checkNameIsOk = (checkName && item.Name.ToUpper().Contains(FilterName.ToUpper()) || !checkName);
                if (checkIdIsOk && checkNameIsOk) return true;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
            return false;
        }
    }
}

物品

public class Item
{
    public int Id { get; set; }
    public string Name { get; set; }
}

基本简单的数据网格,2 列。在每一列中都有一个带有绑定过滤器的TextBox。每个过滤器都有自己的字段,所以在失去焦点后,我可以通过所有过滤器过滤网格。

我的问题是.. 我有很多专栏。这是自定义的数据网格,因此您可以动态添加和删除列,并且有很多重复的代码。基本上这是重复的:

                <DataGridTextColumn.HeaderTemplate>
                    <DataTemplate>
                        <DockPanel>
                            <TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
                            <TextBox DockPanel.Dock="Top"
                                     Text="{Binding DataContext.FilterId, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
                        </DockPanel>
                    </DataTemplate>
                </DataGridTextColumn.HeaderTemplate>

...只有这个&lt;TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterId, ... 会针对不同的列进行更改。

所以,我想,我可以很容易地用这个解决方案替换它,但是现在......我失去了与 ViewModel 中过滤器字段的绑定:

<Window.Resources>
    <DataTemplate x:Key="DataGridHeader">
        <DockPanel>
            <TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
            <TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterName, RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>
        </DockPanel>
    </DataTemplate>
</Window.Resources>
<Grid>
    <DataGrid ItemsSource="{Binding ItemCollection}" AutoGenerateColumns="False">
        <DataGrid.ColumnHeaderStyle>
            <Style TargetType="{x:Type DataGridColumnHeader}">
                <Setter Property="HorizontalContentAlignment" Value="Stretch" />
            </Style>
        </DataGrid.ColumnHeaderStyle>
        <DataGrid.Columns>
            <DataGridTextColumn Header="Id" Binding="{Binding Path=Id}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
            <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}" Width="60" MinWidth="60" MaxWidth="60" HeaderTemplate="{StaticResource DataGridHeader}"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

SOO .. 我在想,为过滤器创建一个 Dictionary,其中键是列的名称,值是我将存储当前过滤器(或 null,如果此时此列没有过滤器)。有点像..

<TextBox x:Name="Foo" DockPanel.Dock="Top" Text="{Binding DataContext.FiltersDictionary[Foo], RelativeSource={RelativeSource AncestorType=Window}, UpdateSourceTrigger=LostFocus}"/>

但是我必须为一个 TextBox 出价。我真的不确定这个解决方案..

我的问题是,如何在上述场景中为 DataTemplate 创建参数?

感谢您的帮助!

PS。它不是重复的。这个问题是关于“如何为 DataTemplate 创建参数”。 “重复”问题是关于字典作为绑定 - 这个问题的潜在解决方案..虽然可能不是。正如另一位用户所建议的那样,可能有完全不同的更好的解决方案来解决这个问题。两种不同的东西。我很震惊,我必须解释这一点

【问题讨论】:

  • @Rekshino 你是认真的吗?它不是重复的。在这里,我问“如何为 DataTemplate 创建参数”,另一个问题是关于“作为绑定的字典”。这个问题的答案可能与字典无关。你甚至读过这两个问题吗?

标签: c# wpf xaml mvvm datagrid


【解决方案1】:

我使用 DependencyProperty 的 Evk 解决方案,而不是使用 Header

<controls:FilterDataGridTextColumn FilterName="Name" Header="Name" Binding="{Binding Path=Name}" Width="200" HeaderTemplate="{StaticResource HeaderTemplate}" />

FilterDataGridTextColumn:

public class FilterDataGridTextColumn : DataGridTextColumn
{
    public static readonly DependencyProperty FilterNameProperty =
        DependencyProperty.Register("FilterName", typeof(string), typeof(FilterDataGridTextColumn));

    public string FilterName
    {
        get { return (string) GetValue(FilterNameProperty); }
        set { SetValue(FilterNameProperty, value); }
    }
}

【讨论】:

    【解决方案2】:

    最简单的方法是不要只依赖 xaml 并添加一些代码来帮助。例如像这样使用TextBoxLoaded 事件:

    <DataTemplate x:Key="DataGridHeader">
        <DockPanel>
            <TextBlock DockPanel.Dock="Top" TextAlignment="Left" Text="{Binding Content, RelativeSource={RelativeSource Mode=TemplatedParent}}" />
            <TextBox DockPanel.Dock="Top" Loaded="OnFilterBoxLoaded" />
        </DockPanel>
    </DataTemplate>
    

    并在加载时设置绑定:

    private void OnFilterBoxLoaded(object sender, RoutedEventArgs e) {
        var tb = (TextBox)sender;
        // find column
        DataGridColumnHeader parent = null;
        DependencyObject current = tb;
        do {
            current = VisualTreeHelper.GetParent(current);
            parent = current as DataGridColumnHeader;
        }
        while (parent == null);
        // setup binding
        var binding = new Binding();
        // use parent column header as name of the filter property
        binding.Path = new PropertyPath("DataContext.Filter" + parent.Column.Header);
        binding.Source = this;
        binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
        tb.SetBinding(TextBox.TextProperty, binding);
    }
    

    您可以使用附加属性来实现相同的目的,但我认为在这种情况下不需要它。

    【讨论】:

    • 谢谢!我没有想到那种方法。但这可以工作!虽然,我可能不能使用parent.Column.Header,因为它可能包含空格、括号等。
    • 好吧,它肯定会起作用,因为我在发布之前对其进行了测试:) 至于标题 - 您可以使用任何其他属性(可能是绑定路径)或创建附加属性并将其用于网格列以提供显式过滤器姓名。如果您在为此使用附加属性时遇到问题,我可以发布一个示例。
    • 为什么要附加财产?我使用了您的解决方案,但使用简单的DependencyProperty 我将其发布为答案。如果这是正确的,你能评论吗?它有效.. 我只是不明白为什么附加属性?
    • @LouisaBickley 是的,这也是正确的。我正在考虑附加属性,因为使用它们您可以使用其他元数据扩展默认类(例如使用过滤器名称扩展 DataGridColumn)。在您的情况下,您确实可以只从列继承,这并不比使用附加属性更差(甚至更好)。
    猜你喜欢
    • 2012-06-21
    • 1970-01-01
    • 1970-01-01
    • 2013-05-29
    • 1970-01-01
    • 2015-09-09
    • 1970-01-01
    • 1970-01-01
    • 2012-06-24
    相关资源
    最近更新 更多