DataGrid 在动态 TabPage 上的绑定属性 (width/sorting)
Binding properties (width/sorting) of DataGrid on dynamic TabPage
我创建了一个小的 wpf 测试项目来演示我的问题。该项目包含一个 wpf window。这个 window 只包含一个 TabControl
。此 TabControl
的页面是从绑定 ItemSource
.
动态创建的
我的XAML:
<Window x:Name="window" x:Class="TestWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TestWpf"
Title="MainWindow" Height="350" Width="525">
<TabControl x:Name="tabControl" BorderThickness="0" ItemsSource ="{Binding MediaLists, ElementName=window, NotifyOnSourceUpdated=True}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:MediaList}">
<TextBlock Padding="2" Text="{Binding MediaTypeName}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate x:Name="contentTemplate" DataType="{x:Type vm:MediaList}">
<DataGrid x:Name="dgMediaList" ItemsSource="{Binding Medias, BindsDirectlyToSource=True}" DockPanel.Dock="Top" AutoGenerateColumns="False" HorizontalGridLinesBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" VerticalGridLinesBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="clmAuthor" Header="Author" Binding="{Binding Author}" IsReadOnly="True" CanUserReorder="False" />
<DataGridTextColumn x:Name="clmTitle" Header="Title" Binding="{Binding Title}" IsReadOnly="True" CanUserReorder="False" />
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
后面的代码是这样的:
public partial class MainWindow : Window
{
private readonly ObservableCollection<MediaList> m_MediaLists = new ObservableCollection<MediaList>();
public ObservableCollection<MediaList> MediaLists { get { return m_MediaLists; } }
public MainWindow()
{
InitializeComponent();
MediaList cdList = new MediaList("CDs");
cdList.Medias.Add(new Media("AuthorCdA", "TitleCdA1"));
cdList.Medias.Add(new Media("AuthorCdA", "TitleCdA2"));
cdList.Medias.Add(new Media("AuthorCdB", "TitleCdB1"));
cdList.Medias.Add(new Media("AuthorCdB", "TitleCdB2"));
MediaList bookList = new MediaList("Books");
bookList.Medias.Add(new Media("AuthorBookA", "TitleBookA1"));
bookList.Medias.Add(new Media("AuthorBookA", "TitleBookA2"));
bookList.Medias.Add(new Media("AuthorBookB", "TitleBookB1"));
bookList.Medias.Add(new Media("AuthorBookB", "TitleBookB2"));
m_MediaLists.Add(cdList);
m_MediaLists.Add(bookList);
}
}
和MediaList
一样:
public class MediaList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private readonly ObservableCollection<Media> m_Medias = new ObservableCollection<Media>();
public string MediaTypeName { get; private set; }
public ObservableCollection<Media> Medias { get { return m_Medias; }}
public MediaList(string typeName)
{
MediaTypeName = typeName;
}
}
和 Media
像这样:
public class Media
{
public string Author { get; private set; }
public string Title { get; private set; }
public Media(string author, string title)
{
Author = author;
Title = title;
}
}
正如您在此处看到的那样,一切正常:
现在的问题是:如何为每个 TabPage
保存 DataGrid
的布局? 当我在 TabPage
之间切换时s,所有页面的列宽保持相同,但排序总是完全丢失。
我想绑定 DataGrid
的列宽以及 多列 排序设置,由用户在 MediaList
的成员中更改=]实例.
因此,为了使这个问题简短,让我们集中讨论列宽。我将成员添加到 MediaList
class:
private DataGridLength m_WidthAuthor = DataGridLength.SizeToCells;
private DataGridLength m_WidthTitle = DataGridLength.SizeToCells;
public DataGridLength WidthAuthor
{
get { return m_WidthAuthor; }
set
{
if (value == m_WidthAuthor) return;
m_WidthAuthor = value;
OnPropertyChanged("WidthAuthor");
}
}
public DataGridLength WidthTitle
{
get { return m_WidthTitle; }
set
{
if (value == m_WidthTitle) return;
m_WidthTitle = value;
OnPropertyChanged("WidthTitle");
}
}
并尝试在 xaml 中设置绑定:
<DataGridTextColumn x:Name="clmAuthor" ... Width="{Binding WidthAuthor, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn x:Name="clmTitle" ... Width="{Binding WidthTitle, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
但不幸的是,这不起作用。我读过几篇 SO 文章,双向绑定很困难。但即使是单向绑定也不适合我。 (我是 wpf/mvvm 的新手,所以可能我在这里使用了一些错误的词...)
在调试器输出中 window 我可以看到这些宽度绑定的错误消息:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=WidthAuthor; DataItem=null; target element is 'DataGridTextColumn' (HashCode=54916642); target property is 'Width' (type 'DataGridLength')
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=WidthTitle; DataItem=null; target element is 'DataGridTextColumn' (HashCode=40809782); target property is 'Width' (type 'DataGridLength')
因此,如果有人能告诉我如何为每个 TabPage
保存那些 DataGrid
属性,我将非常高兴。这个问题的重点是宽度。排序可能是一个新问题的主题(请注意,绑定列的 SortDirection
可能不适用于多列排序)
编辑: 我已将 diag:PresentationTraceSources.TraceLevel=High
添加到 Binding
表达式的其中一列 Width
中,并在调试输出:
System.Windows.Data Warning: 62 : BindingExpression (hash=18270086): Attach to System.Windows.Controls.DataGridTextColumn.Width (hash=37671782)
System.Windows.Data Warning: 64 : BindingExpression (hash=18270086): Use Framework mentor <null>
System.Windows.Data Warning: 67 : BindingExpression (hash=18270086): Resolving source
System.Windows.Data Warning: 69 : BindingExpression (hash=18270086): Framework mentor not found
System.Windows.Data Warning: 65 : BindingExpression (hash=18270086): Resolve source deferred
我对 wpf 太陌生了,无法理解 Framework mentor
是什么,如果这真的应该是 null
。
我在this article的帮助下解决了。不幸的是,我的 wpf 知识仍然很低,我无法解释(以及上述文章的作者)幕后发生的事情以及这是否真的是一个好的解决方案。
这就是我所做的:
实现继承自 `Freezable` 的 `BindingProxy`
其中一个问题是 DataContext
没有继承到 DataGridTextColumn
。所以目的是将其存储在代理中。
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return GetValue(DATA_PROPERTY); }
set { SetValue(DATA_PROPERTY, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DATA_PROPERTY =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
正在将 `BindingProxy` 的实例声明为 `DataGrid` 中的本地资源
我在 xaml 中将此本地资源添加到我的 DataGrid
:
<DataGrid.Resources>
<vm:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
调整绑定
最后我修改了 DataGridTextColum
宽度的绑定表达式:
Width="{Binding Data.WidthAuthor, Source={StaticResource proxy}....
瞧瞧,成功了!
我将继续研究如何为 multi-column 排序。如果我找不到解决方案,我会提出一个新问题。如果在接下来的几天里没有人为此添加解决方案,我将接受我自己的答案。
更新: 我解决了 multi-column 排序问题 。
我创建了一个小的 wpf 测试项目来演示我的问题。该项目包含一个 wpf window。这个 window 只包含一个 TabControl
。此 TabControl
的页面是从绑定 ItemSource
.
我的XAML:
<Window x:Name="window" x:Class="TestWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:TestWpf"
Title="MainWindow" Height="350" Width="525">
<TabControl x:Name="tabControl" BorderThickness="0" ItemsSource ="{Binding MediaLists, ElementName=window, NotifyOnSourceUpdated=True}">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:MediaList}">
<TextBlock Padding="2" Text="{Binding MediaTypeName}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate x:Name="contentTemplate" DataType="{x:Type vm:MediaList}">
<DataGrid x:Name="dgMediaList" ItemsSource="{Binding Medias, BindsDirectlyToSource=True}" DockPanel.Dock="Top" AutoGenerateColumns="False" HorizontalGridLinesBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" VerticalGridLinesBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<DataGrid.Columns>
<DataGridTextColumn x:Name="clmAuthor" Header="Author" Binding="{Binding Author}" IsReadOnly="True" CanUserReorder="False" />
<DataGridTextColumn x:Name="clmTitle" Header="Title" Binding="{Binding Title}" IsReadOnly="True" CanUserReorder="False" />
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
后面的代码是这样的:
public partial class MainWindow : Window
{
private readonly ObservableCollection<MediaList> m_MediaLists = new ObservableCollection<MediaList>();
public ObservableCollection<MediaList> MediaLists { get { return m_MediaLists; } }
public MainWindow()
{
InitializeComponent();
MediaList cdList = new MediaList("CDs");
cdList.Medias.Add(new Media("AuthorCdA", "TitleCdA1"));
cdList.Medias.Add(new Media("AuthorCdA", "TitleCdA2"));
cdList.Medias.Add(new Media("AuthorCdB", "TitleCdB1"));
cdList.Medias.Add(new Media("AuthorCdB", "TitleCdB2"));
MediaList bookList = new MediaList("Books");
bookList.Medias.Add(new Media("AuthorBookA", "TitleBookA1"));
bookList.Medias.Add(new Media("AuthorBookA", "TitleBookA2"));
bookList.Medias.Add(new Media("AuthorBookB", "TitleBookB1"));
bookList.Medias.Add(new Media("AuthorBookB", "TitleBookB2"));
m_MediaLists.Add(cdList);
m_MediaLists.Add(bookList);
}
}
和MediaList
一样:
public class MediaList : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
private readonly ObservableCollection<Media> m_Medias = new ObservableCollection<Media>();
public string MediaTypeName { get; private set; }
public ObservableCollection<Media> Medias { get { return m_Medias; }}
public MediaList(string typeName)
{
MediaTypeName = typeName;
}
}
和 Media
像这样:
public class Media
{
public string Author { get; private set; }
public string Title { get; private set; }
public Media(string author, string title)
{
Author = author;
Title = title;
}
}
正如您在此处看到的那样,一切正常:
现在的问题是:如何为每个 TabPage
保存 DataGrid
的布局? 当我在 TabPage
之间切换时s,所有页面的列宽保持相同,但排序总是完全丢失。
我想绑定 DataGrid
的列宽以及 多列 排序设置,由用户在 MediaList
的成员中更改=]实例.
因此,为了使这个问题简短,让我们集中讨论列宽。我将成员添加到 MediaList
class:
private DataGridLength m_WidthAuthor = DataGridLength.SizeToCells;
private DataGridLength m_WidthTitle = DataGridLength.SizeToCells;
public DataGridLength WidthAuthor
{
get { return m_WidthAuthor; }
set
{
if (value == m_WidthAuthor) return;
m_WidthAuthor = value;
OnPropertyChanged("WidthAuthor");
}
}
public DataGridLength WidthTitle
{
get { return m_WidthTitle; }
set
{
if (value == m_WidthTitle) return;
m_WidthTitle = value;
OnPropertyChanged("WidthTitle");
}
}
并尝试在 xaml 中设置绑定:
<DataGridTextColumn x:Name="clmAuthor" ... Width="{Binding WidthAuthor, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn x:Name="clmTitle" ... Width="{Binding WidthTitle, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
但不幸的是,这不起作用。我读过几篇 SO 文章,双向绑定很困难。但即使是单向绑定也不适合我。 (我是 wpf/mvvm 的新手,所以可能我在这里使用了一些错误的词...)
在调试器输出中 window 我可以看到这些宽度绑定的错误消息:
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=WidthAuthor; DataItem=null; target element is 'DataGridTextColumn' (HashCode=54916642); target property is 'Width' (type 'DataGridLength')
System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=WidthTitle; DataItem=null; target element is 'DataGridTextColumn' (HashCode=40809782); target property is 'Width' (type 'DataGridLength')
因此,如果有人能告诉我如何为每个 TabPage
保存那些 DataGrid
属性,我将非常高兴。这个问题的重点是宽度。排序可能是一个新问题的主题(请注意,绑定列的 SortDirection
可能不适用于多列排序)
编辑: 我已将 diag:PresentationTraceSources.TraceLevel=High
添加到 Binding
表达式的其中一列 Width
中,并在调试输出:
System.Windows.Data Warning: 62 : BindingExpression (hash=18270086): Attach to System.Windows.Controls.DataGridTextColumn.Width (hash=37671782)
System.Windows.Data Warning: 64 : BindingExpression (hash=18270086): Use Framework mentor <null>
System.Windows.Data Warning: 67 : BindingExpression (hash=18270086): Resolving source
System.Windows.Data Warning: 69 : BindingExpression (hash=18270086): Framework mentor not found
System.Windows.Data Warning: 65 : BindingExpression (hash=18270086): Resolve source deferred
我对 wpf 太陌生了,无法理解 Framework mentor
是什么,如果这真的应该是 null
。
我在this article的帮助下解决了。不幸的是,我的 wpf 知识仍然很低,我无法解释(以及上述文章的作者)幕后发生的事情以及这是否真的是一个好的解决方案。
这就是我所做的:
实现继承自 `Freezable` 的 `BindingProxy`
其中一个问题是 DataContext
没有继承到 DataGridTextColumn
。所以目的是将其存储在代理中。
public class BindingProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
public object Data
{
get { return GetValue(DATA_PROPERTY); }
set { SetValue(DATA_PROPERTY, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DATA_PROPERTY =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
正在将 `BindingProxy` 的实例声明为 `DataGrid` 中的本地资源
我在 xaml 中将此本地资源添加到我的 DataGrid
:
<DataGrid.Resources>
<vm:BindingProxy x:Key="proxy" Data="{Binding}" />
</DataGrid.Resources>
调整绑定
最后我修改了 DataGridTextColum
宽度的绑定表达式:
Width="{Binding Data.WidthAuthor, Source={StaticResource proxy}....
瞧瞧,成功了!
我将继续研究如何为 multi-column 排序。如果我找不到解决方案,我会提出一个新问题。如果在接下来的几天里没有人为此添加解决方案,我将接受我自己的答案。
更新: 我解决了 multi-column 排序问题