Window.Resources中的WPF DataTemplate绑定参数 [英] WPF DataTemplate binding parameter in Window.Resources
问题描述
视图
< 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 =HorizontalContentAlignmentValue =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 =HorizontalContentAlignmentValue =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屋!