我有一个SelectionMode =“Single”SelectionUnit =“Cell”的DataGrid。 目前,当显示 DataGrid 时,最初没有选择任何单元格。
我需要满足两种情况,要求我了解其中之一:
<DataGrid Margin="0,5,0,0"
IsReadOnly="True"
ItemsSource="{Binding DataTableViewModel.DataView}"
AutoGenerateColumns="true"
ScrollViewer.CanContentScroll="True"
CanUserResizeColumns="True"
CanUserReorderColumns="True"
CanUserSortColumns="True"
CanUserAddRows="false"
ColumnWidth="auto"
EnableRowVirtualization="True"
EnableColumnVirtualization="True"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.VirtualizationMode="Standard"
VirtualizingPanel.IsVirtualizing="True"
SelectionMode="Single"
SelectionUnit="Cell"
VerticalAlignment="Top"/>
还有以下附加属性可供绑定:
已选项目
CurrentCell(但是,这是一个结构体,而不是对 DataGridCell 容器的实际引用。)
选定索引
但是,我不知道该使用哪一个,也不知道如何在 ViewModel 中分配它,因为似乎没有一个接受 [0,0] 坐标。
如果我可以确定使用哪个属性来标识所选单元格,我可以包含一些内容,以便在更新所选单元格属性时,我可以确定是否需要单元格值或所有列值,并相应地更新该列表。
我还应该添加,加载控件时不会加载数据,这是视图模型中的一个片段,显示了我如何添加数据:
class DataTableViewModel : PropertyChangedBase
{
// notify child classes the DataTable has been loaded
protected event EventHandler<EventArgs> DataTableLoaded;
public DataView DataView { get { return _dataView; } set { _dataView = value; NotifyPropertyChanged(); } }
DataView _dataView;
DataTable _dataTable;
public DataTable DataTable
{
get { return _dataTable; }
set
{
_dataTable = value;
DataView = value.DefaultView;
if (DataTableLoaded== null)
return;
DataTableLoaded.Invoke(this, new EventArgs());
NotifyPropertyChanged();
// Can I put something here to set the selected cell?
}
}
所以,我的问题主要是:当 DataGrid 首次显示时,如何将 ViewModel 中的活动单元格位置设置为 [0,0]?
进一步研究:
我发现了一个类似的问题,但答案似乎只处理行而不是单元格:
如何使用 MVVM 应用程序在 WPF 中以编程方式设置 DataGrid 的选定项?
这是一项相当困难的任务,特别是考虑到MVVM模式的实现。您必须在 View 和 ViewModel 之间提供某种双向通信才能选择单元格。
执行此操作的一种方法是:
using Simplified; // Space with my implementations of ViewModelBase and RelayCommand.
using System.Data;
namespace Core2023.SO.Jase99.Question77673006
{
public class CellInfoData : ViewModelBase
{
public bool IsSelected { get => Get<bool>(); set => Set(value); }
public int Row { get; }
public int Column { get; }
public CellInfoData(int row, int column)
{
Row = row;
Column = column;
}
}
public class CellContentInfoData<T> : CellInfoData
{
public T Content { get => Get<T>(); set => Set(value); }
public CellContentInfoData(int row, int column) : base(row, column) { }
}
public class CellIntInfoData : CellContentInfoData<int>
{
public CellIntInfoData(int row, int column) : base(row, column) { }
}
public class CellStringInfoData : CellContentInfoData<string>
{
public CellStringInfoData(int row, int column) : base(row, column) { }
}
public class SomeViewModel : ViewModelBase
{
public DataTable Table { get => Get<DataTable>(); private set => Set(value); }
public CellInfoData? LastSelectedCell { get => Get<CellInfoData>(); private set => Set(value); }
public SomeViewModel()
{
DataTable table = new ();
table.Columns.Add(new DataColumn("Number", typeof(CellIntInfoData)));
table.Columns.Add(new DataColumn("Text", typeof(CellStringInfoData)));
foreach (var item in new (int, string)[] { (1, "First"), (2, "Second"), (3, "Third" ) })
{
DataRow row = table.NewRow();
CellInfoData cell = new CellIntInfoData(table.Rows.Count, 0) { Content = item.Item1 };
cell.PropertyChanged += CellPropertyChanged;
row[0] = cell;
cell = new CellStringInfoData(table.Rows.Count, 1) { Content = item.Item2 };
cell.PropertyChanged += CellPropertyChanged;
row[1] = cell;
table.Rows.Add(row);
}
Table = table;
}
private void CellPropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (sender is CellInfoData cell && e.PropertyName == nameof(CellInfoData.IsSelected))
{
if (cell.IsSelected)
{
LastSelectedCell = cell;
}
}
}
public RelayCommand SelectedCell00 => GetCommand(() =>
{
((CellInfoData)Table.Rows[0][0]).IsSelected = true;
});
}
}
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
namespace Core2023.SO.Jase99.Question77673006
{
public partial class SelectedCellWindow : Window
{
public SelectedCellWindow()
{
InitializeComponent();
}
}
public static class DataGridCellHelper
{
const string stringTemplate =
@"
<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation""
xmlns:local=""clr-namespace:Core2023.SO.Jase99.Question77673006;assembly=Core2023"">
<ContentControl Content=""{{Binding {0}}}""
local:DependencyObjectHelper.First=""{{Binding IsSelected, RelativeSource={{RelativeSource AncestorType=DataGridCell}}}}""
local:DependencyObjectHelper.Second=""{{Binding Content.IsSelected, RelativeSource={{RelativeSource Self}}}}""/>
</DataTemplate>
";
//local:DependencyObjectHelper.Second=""{{Binding Content.IsSelected, RelativeSource={{RelativeSource Self}}}}""
public static readonly EventHandler<DataGridAutoGeneratingColumnEventArgs> OnGeneratingColumn = (sender, e) =>
{
e.Column = new DataGridTemplateColumn()
{
Header = e.Column.Header,
CellTemplate = (DataTemplate)XamlReader.Parse(string.Format(stringTemplate, e.PropertyName))
};
};
}
public static class DependencyObjectHelper
{
public static object GetFirst(DependencyObject obj)
{
return obj.GetValue(FirstProperty);
}
public static void SetFirst(DependencyObject obj, object value)
{
obj.SetValue(FirstProperty, value);
}
// Using a DependencyProperty as the backing store for First. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FirstProperty =
DependencyProperty.RegisterAttached(
"First",
typeof(bool?),
typeof(DependencyObjectHelper),
new FrameworkPropertyMetadata(null)
{
PropertyChangedCallback = (d,e) => d.SetValue(SecondProperty, e.NewValue),
BindsTwoWayByDefault = true
});
public static object GetSecond(DependencyObject obj)
{
return obj.GetValue(SecondProperty);
}
public static void SetSecond(DependencyObject obj, object value)
{
obj.SetValue(SecondProperty, value);
}
// Using a DependencyProperty as the backing store for Second. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SecondProperty =
DependencyProperty.RegisterAttached(
"Second",
typeof(bool?),
typeof(DependencyObjectHelper),
new FrameworkPropertyMetadata((bool?) null)
{
PropertyChangedCallback = (d, e) => d.SetValue(FirstProperty, e.NewValue),
BindsTwoWayByDefault = true
});
}
}
<Window x:Class="Core2023.SO.Jase99.Question77673006.SelectedCellWindow"
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:local="clr-namespace:Core2023.SO.Jase99.Question77673006"
xmlns:converters="clr-namespace:Converters;assembly=CommonCore"
mc:Ignorable="d"
Title="SelectedCellWindow" Height="450" Width="800"
DataContext="{DynamicResource vm}">
<Window.Resources>
<local:SomeViewModel x:Key="vm"/>
<!--<DataTemplate xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Core2023.SO.Jase99.Question77673006"
x:Key="contentControl">
<ContentControl Content="{Binding }"
local:DependencyObjectHelper.First="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=DataGridCell}}"
local:DependencyObjectHelper.Second="{Binding Content.IsSelected, RelativeSource={RelativeSource Self}}"/>
</DataTemplate>-->
<DataTemplate DataType="{x:Type local:CellIntInfoData}">
<TextBlock Text="{Binding Content, StringFormat='Number: 0'}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:CellStringInfoData}">
<TextBlock>
<Run Text="Text:"/>
<Run Text="{Binding Content}"/>
</TextBlock>
</DataTemplate>
</Window.Resources>
<UniformGrid Rows="1">
<DataGrid ItemsSource="{Binding Table}"
CanUserAddRows="False"
AutoGeneratingColumn="{x:Static local:DataGridCellHelper.OnGeneratingColumn}"
AutoGenerateColumns="True"
SelectionMode="Single"
SelectionUnit="Cell">
</DataGrid>
<UniformGrid Columns="1">
<TextBlock>
<Run Text="{Binding LastSelectedCell.Row, Mode=OneWay}"/>
<LineBreak/>
<Run Text="{Binding LastSelectedCell.Column, Mode=OneWay}"/>
</TextBlock>
<Button Content="Selected Cell 0,0"
Command="{Binding SelectedCell00}"/>
</UniformGrid>
</UniformGrid>
</Window>