Window.Resources中的WPF DataTemplate绑定参数 [英] WPF DataTemplate binding parameter in Window.Resources

查看:143
本文介绍了Window.Resources中的WPF DataTemplate绑定参数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



视图

 < 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 =MainWindowHeight =300Width =300>
< Window.DataContext>
< viewModels:MainWindowViewModel />
< /Window.DataContext>
< Window.Resources>
< DataTemplate x:Key =DataGridHeader>
& DockPanel>
< TextBlock DockPanel.Dock =TopTextAlignment =LeftText ={Binding Content,RelativeSource = {RelativeSource Mode = TemplatedParent}}/>
< TextBox DockPanel.Dock =TopText ={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 =Horizo​​ntalContentAlignmentValue =Stretch/>
< / Style>
< /DataGrid.ColumnHeaderStyle>

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

ViewModel

 命名空间TestDataGridApp.ViewModels 
{
using System;
使用System.Collections.ObjectModel;
使用System.ComponentModel;
使用System.Windows.Data;
使用TestDataGridApp.Entities;
使用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>项目
{
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)返回true;
if(item == null)return false;

bool checkIdIsOk =(checkId&& item.Id == int.Parse(FilterId)||!checkId);
bool checkNameIsOk =(checkName&& item.Name.ToUpper()。包含(FilterName.ToUpper())||!checkName);
if(checkIdIsOk&& checkNameIsOk)返回true;
}
catch(异常e)
{
Console.WriteLine(e);
}
返回false;
}
}
}

/ strong>

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

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



我的问题是..我有很多列。这是定制的datagrid,因此您可以随时添加和删除列,并且有很多重复的代码。基本上这是重复的:

 < DataGridTextColumn.HeaderTemplate> 
< DataTemplate>
& DockPanel>
< TextBlock DockPanel.Dock =TopTextAlignment =LeftText ={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 =TopTextAlignment =LeftText ={Binding Content,RelativeSource = {RelativeSource Mode = TemplatedParent}}/>
< TextBox DockPanel.Dock =TopText ={Binding DataContext.FilterName,RelativeSource = {RelativeSource AncestorType = Window},UpdateSourceTrigger = LostFocus}/ >
< / DockPanel>
< / DataTemplate>
< /Window.Resources>
< Grid>
< DataGrid ItemsSource =绑定ItemCollection}AutoGenerateColumns =False>
< DataGrid.ColumnHeaderStyle>
< Style TargetType ={x:Type DataGridColumnHeader}>
< Setter Property =Horizo​​ntalContentAlignmentValue =Stretch/>
< / Style>
< /DataGrid.ColumnHeaderStyle>
< DataGrid.Columns>
< DataGridTextColumn Header =IdBinding ={Binding Path = Id}Width =60MinWidth =60MaxWidth =60HeaderTemplate ={StaticResource DataGridHeader}/>
< DataGridTextColumn Header =NameBinding ={Binding Path = Name}Width =60MinWidth =60MaxWidth =60HeaderTemplate ={StaticResource DataGridHeader}/>
< /DataGrid.Columns>
< / DataGrid>
< / Grid>

SOO ..我在想,要创建一个字典用于过滤器,其中key将是列的名称,而在值中,我将存储当前过滤器(如果该列目前没有过滤器,则为null)。一些东西像..

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

但是我必须为一个TextBox Biding上下文..我真的不知道这个解决方案。



我的问题是,如何在上述方案中为DataTemplate创建一个参数?感谢您的帮助!



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

解决方案

最简单的方法是不要仅依靠xaml并添加一些代码来帮助。例如使用加载的 TextBox 事件,如下所示:

 < DataTemplate x:Key =DataGridHeader> 
& DockPanel>
< TextBlock DockPanel.Dock =TopTextAlignment =LeftText ={Binding Content,RelativeSource = {RelativeSource Mode = TemplatedParent}}/>
< TextBox DockPanel.Dock =TopLoaded =OnFilterBoxLoaded/>
< / DockPanel>
< / DataTemplate>

加载时设置绑定:

  private void OnFilterBoxLoaded(object sender,RoutedEventArgs e){
var tb =(TextBox)sender;
//查找列
DataGridColumnHeader parent = null;
DependencyObject current = tb;
do {
current = VisualTreeHelper.GetParent(current);
parent =当前为DataGridColumnHeader;
}
while(parent == null);
//设置绑定
var binding = new Binding();
//使用父列标题作为过滤器属性的名称
binding.Path = new PropertyPath(DataContext.Filter+ parent.Column.Header);
binding.Source = this;
binding.UpdateSourceTrigger = UpdateSourceTrigger.LostFocus;
tb.SetBinding(TextBox.TextProperty,binding);
}

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


I'm creating a datagrid, with filters in the column headers. It works, but I don't think it's a good approach. Let me show you the code, very simple example:

The View

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

ViewModel

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

The Item

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

Basically simple datagrid, 2 columns. In each column there is a TextBox with binded filter. Each filter has its own field, so after the focus is lost, I can filter the grid by all filters.

My issue is.. I have a lot of columns. This is customized datagrid, so you can add and remove columns on the fly and there's a lot of duplicated code. Basically this is duplicated:

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

... only this <TextBox DockPanel.Dock="Top" Text="{Binding DataContext.FilterId, ... is changing for different columns.

So, I thought, I can easily replace it with this solution, but now.. I lost binding to my filter fields in the 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.. I was thinking, to create a Dictionary for filters, where key would be the name of the column and in value I will store current filter (or null, if there's no filter at the moment for this column). Something like..

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

But then I have to Biding contexts.. for one TextBox. I'm really not sure about this solution..

My question will be, how to create a parameter for DataTemplate in the above scenario?

Thanks for help!

PS. It's not a duplicate. This question is about "how to create a parameter for DataTemplate". The "duplicated" question is about dictionary as a binding - a potential solution for this question.. although probably NOT. As another user suggested there might be totally different, better solution to solve this problem. Two different things. I'm shocked that I have to explain this

解决方案

Easiest way is to not rely only on xaml and add some code to help. For example use Loaded event of your TextBox like this:

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

And setup binding when it is loaded:

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

You can use attached property to achieve the same, but I don't think it's needed in this case.

这篇关于Window.Resources中的WPF DataTemplate绑定参数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆