#c# #wpf #binding #datagrid
#c# #wpf #привязка #datagrid
Вопрос:
Я пытаюсь определить новый шаблон столбца для datagrid, который я могу повторно использовать в своем приложении, но когда я пытаюсь его использовать, я получаю:
Система.Windows.Ошибка данных: 2: Не удается найти управляющий FrameworkElement или FrameworkContentElement для целевого элемента. Выражение привязки: Path= CanLogin; DataItem= null; целевой элемент — ‘DataGridBetterCheckBoxColumn’ (хэш-код = 56040243); целевое свойство — ‘IsChecked’ (тип ‘Object’)
XAML для столбца:
<DataGridTemplateColumn x:Class="BACSFileGenerator.UserControls.DataGridBetterCheckBoxColumn"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BACSFileGenerator.UserControls"
mc:Ignorable="d"
x:Name="ColumnRoot"
>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding isChecked, Source={x:Reference Name=ColumnRoot}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Код, лежащий в основе:
using System.Windows;
using System.Windows.Controls;
namespace BACSFileGenerator.UserControls
{
public partial class DataGridBetterCheckBoxColumn : DataGridTemplateColumn
{
public object isChecked
{
get { return (object)GetValue(isCheckedProperty); }
set { SetValue(isCheckedProperty, value); }
}
public static readonly DependencyProperty isCheckedProperty =
DependencyProperty.Register("isChecked", typeof(object),
typeof(DataGridBetterCheckBoxColumn), new PropertyMetadata(null));
public DataGridBetterCheckBoxColumn()
{
InitializeComponent();
}
}
}
Затем я пытаюсь использовать его следующим образом:
<DataGrid Margin="0,0,0,10" ItemsSource="{Binding UserAccessGrid}" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="User" Binding="{Binding User}" IsReadOnly="True"/>
<uc:DataGridBetterCheckBoxColumn Header="Login" isChecked="{Binding CanLogin}"/>
<uc:DataGridBetterCheckBoxColumn Header="Export Payments" isChecked="{Binding canExportPayments}"/>
<uc:DataGridBetterCheckBoxColumn Header="Create File Layouts" isChecked="{Binding canCreateFileLayouts}"/>
<uc:DataGridBetterCheckBoxColumn Header="Change User Access" isChecked="{Binding canChangeUserAccess}"/>
</DataGrid.Columns>
</DataGrid>
Кто-нибудь может объяснить мне, как правильно это сделать?
Ответ №1:
Допустим, у нас есть
public class ViewModel
{
public bool CanBeUsed {get;set;}
public List<Employee> Employees{get;set;}
}
Несколько моментов, которые могли вас смутить :
-
Для свойства будет создан только один
DataGridBetterCheckBoxColumn
экземпляр. Несколько записей не означают несколько экземпляров столбца для свойства. ВместоDataGridCell
этого для каждого создается несколькоDataGridColumn
.Но
DataGridColumn
это не aFrameworkElement
илиVisual
so, оно не будет отображаться вVisualTree
, и, поскольку это неFrameworkElement
так, у него нетDataContext
свойства. БезDataContext
того, как выBinding
будете работать? Спросите себя. Поскольку у этогоColumn
не может быть своегоDataContext
набора, поэтому он должен иметь либо aElementName
, либо aSource
, либо aRelativeSource
для егоBinding
работы.Теперь мы знаем, что будет только один экземпляр a
DataGridColumn
, поэтому, естественно, онBinding
должен (должен) использоватьDataContext
(свойство коллекции будет частью этого) ofDataGrid
.Теперь посмотрите
Binding
, где находится егоSource
/RelativeSource
? Их нет. Теперь, будетRelativeSource
ли здесь какой-то смысл? AsDataGridColumn
не отображается вVisualTree
, поэтомуRelativeSource
здесь не применяется. У нас осталосьSource
свойство. Что мы должны установить сейчас дляSource
? ВведитеDataContext Inheritance
.Наследование DataContext
DataContext
наследование будет работать только дляFrameworkElement
подключенных черезVisualTree
. Итак, нам нужен механизм, с помощью которого мы можем перенести этоDataContext
на нашDataGridColumn
. ВведитеBinding Proxy
.public class BindingProxy : Freezable { #region Overrides of Freezable protected override Freezable CreateInstanceCore() { return new BindingProxy(); } #endregion public object Data { get { return (object)GetValue(DataProperty); } set { SetValue(DataProperty, value); } } // Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc... public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); }
Если мы объявим экземпляр этого
BindingProxy
как aResource
, мы можем получить нашSource
.<DataGrid Margin="0,52,0,10" ItemsSource="{Binding Records}" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False"> <DataGrid.Resources> <uc:BindingProxy x:Key="FE" Data="{Binding}"/> </DataGrid.Resources> <DataGrid.Columns> <DataGridTextColumn x:Name="dgt" Header="User" Binding="{Binding User}" IsReadOnly="True"/> <uc:DataGridBetterCheckBoxColumn isChecked="{Binding Data.CanBeUsed, Source={StaticResource FE}}" Header="CanLogin"/> </DataGrid.Columns> </DataGrid>
Теперь вы увидите, что ваша неприятность
Binding Error
исчезла. -
Чтобы ваша
CheckBox
привязка работала должным образом, вам необходимо обработать ееLoaded
событие.<DataGridTemplateColumn.CellTemplate> <DataTemplate> <CheckBox Loaded="CheckBox_Loaded"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate>
Код :
void CheckBox_Loaded(object sender, RoutedEventArgs e) { Binding b = new Binding(); b.Path = new PropertyPath("isChecked"); b.Mode = BindingMode.TwoWay; b.Source = this; CheckBox cb = sender as CheckBox; BindingOperations.SetBinding(cb , CheckBox.IsCheckedProperty, b); }
Но теперь у нас есть одна логическая проблема. Все наши
CheckBox
теперь ограниченыDataContext
свойствомCanBeUsed
, которое останется неизменным. Возможно, вы думаете, чтоCanBeUsed
это должно быть свойствоEmployee
, которое являетсяItemsSource
, а неDataContext
ofDataGrid
. Итак, когда вы устанавливаете / снимаете флажок anyCheckBox
, все будут отвечать одинаково.
Но мы хотим привязать наше isChecked
свойство к некоторому свойству Employee
записи, которое будет оставаться diff для каждого DataGridRow
. Итак, теперь нам нужно изменить наше определение isChecked
, после чего весь код будет выглядеть следующим образом :
public partial class DataGridBetterCheckBoxColumn : DataGridTemplateColumn
{
public BindingBase isChecked { get; set; }
public DataGridBetterCheckBoxColumn()
{
InitializeComponent();
}
void CheckBox_Loaded(object sender, RoutedEventArgs e)
{
CheckBox cb = sender as CheckBox;
BindingOperations.SetBinding(cb , CheckBox.IsCheckedProperty, isChecked);
}
}
Использование :
<uc:DataGridBetterCheckBoxColumn isChecked="{Binding CanLogin, Mode=TwoWay}" Header="CanLogin"/>
Если я пропустил какой-либо момент, дайте мне знать.