Window.Resources

时间:2017-03-24 17:51:27

标签: c# wpf xaml mvvm datagrid

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

视图

<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。每个过滤器都有自己的字段,因此在失去焦点后,我可以通过所有过滤器过滤网格。

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

                <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>

...只有这个<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,其中key是列的名称,在值中我将存储当前过滤器(如果此时没有过滤器,则为null这一栏)。像...这样的东西。

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

但是我必须将Biding上下文...用于一个TextBox。我真的不确定这个解决方案..

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

感谢您的帮助!

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

2 个答案:

答案 0 :(得分:2)

最简单的方法是不仅仅依赖于xaml并添加一些代码来提供帮助。例如,使用Loaded这样的TextBox事件:

<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);
}

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

答案 1 :(得分: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); }
    }
}