Как привязать DataGridColumn.Visibility к DataGridColumn?
-
20-08-2019 - |
Вопрос
У меня проблема, похожая на следующий пост:
Видимость привязки DataGridTextColumn Silverlight DataGridTextColumn
Мне нужно, чтобы столбец в сетке данных Silverlight был видимым / свернутым на основе значения в ViewModel.Чтобы добиться этого, я пытаюсь привязать свойство Visibility к ViewModel .Однако вскоре я обнаружил, что свойство Visibility не является DependencyProperty, поэтому его нельзя привязать.
Чтобы решить эту проблему, я попытался создать подкласс моего собственного DataGridTextColumn .С помощью этого нового класса я создал DependencyProperty, который в конечном итоге вносит изменения в свойство DataGridTextColumn.Visibility.Это работает хорошо, если я не привязываю данные.В тот момент, когда я привязываю данные к своему новому свойству, происходит сбой с исключением AG_E_PARSER_BAD_PROPERTY_VALUE.
public class MyDataGridTextColumn : DataGridTextColumn
{
#region public Visibility MyVisibility
public static readonly DependencyProperty MyVisibilityProperty =
DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(MyDataGridTextColumn), new PropertyMetadata(Visibility.Visible, OnMyVisibilityPropertyChanged));
private static void OnMyVisibilityPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var @this = d as MyDataGridTextColumn;
if (@this != null)
{
@this.OnMyVisibilityChanged((Visibility)e.OldValue, (Visibility)e.NewValue);
}
}
private void OnMyVisibilityChanged(Visibility oldValue, Visibility newValue)
{
Visibility = newValue;
}
public Visibility MyVisibility
{
get { return (Visibility)GetValue(MyVisibilityProperty); }
set { SetValue(MyVisibilityProperty, value); }
}
#endregion public Visibility MyVisibility
}
Вот небольшой фрагмент XAML.
<DataGrid ....>
<DataGrid.Columns>
<MyDataGridTextColumn Header="User Name"
Foreground="#FFFFFFFF"
Binding="{Binding User.UserName}"
MinWidth="150"
CanUserSort="True"
CanUserResize="False"
CanUserReorder="True"
MyVisibility="{Binding Converter={StaticResource BoolToVisibilityConverter}, Path=ShouldShowUser}"/>
<DataGridTextColumn .../>
</DataGrid.Columns>
</DataGrid>
Пара важных фактов.
- Конвертер действительно определен выше в локальных ресурсах.
- Конвертер правильный, он используется во многих других местах решения.
- Если я заменю синтаксис {Binding} для свойства MyVisibility на "Свернутый", столбец фактически исчезнет.
- Если я создам новое свойство DependencyProperty (т.е.string Foo), и привязавшись к нему, я также получаю исключение AG_E_PARSER_BAD_PROPERTY_VALUE.
У кого-нибудь есть какие-нибудь идеи относительно того, почему это не работает?
Решение
Вот решение, к которому я пришел, используя небольшой взлом.
Во-первых, вам нужно наследовать от DataGrid.
public class DataGridEx : DataGrid
{
public IEnumerable<string> HiddenColumns
{
get { return (IEnumerable<string>)GetValue(HiddenColumnsProperty); }
set { SetValue(HiddenColumnsProperty, value); }
}
public static readonly DependencyProperty HiddenColumnsProperty =
DependencyProperty.Register ("HiddenColumns",
typeof (IEnumerable<string>),
typeof (DataGridEx),
new PropertyMetadata (HiddenColumnsChanged));
private static void HiddenColumnsChanged(object sender,
DependencyPropertyChangedEventArgs args)
{
var dg = sender as DataGrid;
if (dg==null || args.NewValue == args.OldValue)
return;
var hiddenColumns = (IEnumerable<string>)args.NewValue;
foreach (var column in dg.Columns)
{
if (hiddenColumns.Contains ((string)column.GetValue (NameProperty)))
column.Visibility = Visibility.Collapsed;
else
column.Visibility = Visibility.Visible;
}
}
}
В Таблица данных класс добавляет новый DP для скрытия столбцов на основе x:Имя из a DataGridColumn Столбец DataGridColumn и его потомки.
Для использования в вашем XAML:
<my:DataGridEx x:Name="uiData"
DataContext="{Binding SomeDataContextFromTheVM}"
ItemsSource="{Binding Whatever}"
HiddenColumns="{Binding HiddenColumns}">
<sdk:DataGridTextColumn x:Name="uiDataCountOfItems">
Header="Count"
Binding={Binding CountOfItems}"
</sdk:DataGridTextColumn>
</my:DataGridEx>
Вам нужно добавить их в свою ViewModel или любой другой контекст данных, который вы используете.
private IEnumerable<string> _hiddenColumns;
public IEnumerable<string> HiddenColumns
{
get { return _hiddenColumns; }
private set
{
if (value == _hiddenColumns)
return;
_hiddenColumns = value;
PropertyChanged (this, new PropertyChangedEventArgs("HiddenColumns"));
}
}
public void SomeWhereInYourCode ()
{
HiddenColumns = new List<string> {"uiDataCountOfItems"};
}
Чтобы не показывать, вам нужно только удалить соответствующее имя из списка или создать его заново без нескрытого имени.
Другие советы
У меня есть другое решение этой проблемы, которое использует подход, аналогичный свойству "Binding", которое вы найдете в DataGridTextColumn .Поскольку классы столбцов являются DependencyObjects, вы не можете напрямую привязывать к ним данные, но если вы добавите ссылку на FrameworkElement, который реализует INotifyPropertyChanged, вы можете передать привязку данных элементу, а затем использовать свойство dependency для уведомления столбца об изменении привязки данных.
Следует отметить одну вещь: наличие привязки к самому столбцу вместо сетки, вероятно, будет означать, что вы захотите использовать DataContextProxy - Файл DataContextProxy чтобы получить доступ к полю, к которому вы хотите привязать видимость (привязка столбца по умолчанию будет соответствовать области ItemSource).
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace XYZ.Controls
{
public class ExtendedDataGridTextColumn : DataGridTextColumn
{
private readonly Notifier _e;
private Binding _visibilityBinding;
public Binding VisibilityBinding
{
get { return _visibilityBinding; }
set
{
_visibilityBinding = value;
_e.SetBinding(Notifier.MyVisibilityProperty, _visibilityBinding);
}
}
public ExtendedDataGridTextColumn()
{
_e = new Notifier();
_e.PropertyChanged += ToggleVisibility;
}
private void ToggleVisibility(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Visibility")
this.Visibility = _e.MyVisibility;
}
//Notifier class is just used to pass the property changed event back to the column container Dependency Object, leaving it as a private inner class for now
private class Notifier : FrameworkElement, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public Visibility MyVisibility
{
get { return (Visibility)GetValue(MyVisibilityProperty); }
private set { SetValue(MyVisibilityProperty, value); }
}
public static readonly DependencyProperty MyVisibilityProperty = DependencyProperty.Register("MyVisibility", typeof(Visibility), typeof(Notifier), new PropertyMetadata(MyVisibilityChanged));
private static void MyVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var n = d as Notifier;
if (n != null)
{
n.MyVisibility = (Visibility) e.NewValue;
n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
}
}
}
}
}
Столбец datagrid наследуется от DependencyObject вместо FrameworkElement.В WPF это не было бы большой проблемой...но в silverlight вы можете привязываться только к объектам FrameworkElement.Таким образом, при попытке вы получаете описательное сообщение об ошибке AG_E_PARSER_BAD_PROPERTY_VALUE.
Я не знаю, насколько это поможет, но я сам столкнулся с проблемой отсутствия свойства зависимости со столбцами таблицы данных в моем последнем проекте.Что я сделал, чтобы обойти это, так это создал событие в модели представления столбцов сетки, затем, когда сетка собирается в клиенте, используйте замыкание, чтобы подписать столбец сетки на модель представления столбцов.Моя конкретная проблема заключалась в ширине.Он начинается с класса view model для столбца grid, который выглядит примерно как этот псевдокод:
public delegate void ColumnResizedEvent(double width);
public class GridColumnViewModel : ViewModelBase
{
public event ColumnResizedEvent ColumnResized;
public void Resize(double newContainerWidth)
{
// some crazy custom sizing calculations -- don't ask...
ResizeColumn(newWidth);
}
public void ResizeColumn(double width)
{
var handler = ColumnResized;
if (handler != null)
handler(width);
}
}
Затем есть код, который собирает сетку:
public class CustomGrid
{
public CustomGrid(GridViewModel viewModel)
{
// some stuff that parses control metadata out of the view model.
// viewModel.Columns is a collection of GridColumnViewModels from above.
foreach(var column in viewModel.Columns)
{
var gridCol = new DataGridTextColumn( ... );
column.ColumnResized += delegate(double width) { gridCol.Width = new DataGridLength(width); };
}
}
}
Когда размер сетки данных изменяется в приложении, регистрируется событие resize и вызывает метод resize для viewmodel, к которому привязана сетка.Это, в свою очередь, вызывает метод изменения размера каждой модели представления столбцов сетки.Затем модель представления столбцов сетки поднимает ColumnResized
событие, на которое подписан текстовый столбец таблицы данных, и его ширина обновляется.
Я понимаю, что это напрямую не решает вашу проблему, но это был способ, которым я мог "привязать" модель представления к столбцу таблицы данных, когда у нее нет свойств зависимости.Закрытие - это простая конструкция, которая прекрасно инкапсулирует поведение, которого я хотел, и вполне понятна тому, кто идет позади меня.Я думаю, не слишком сложно представить, как это можно было бы модифицировать, чтобы справиться с изменением видимости.Вы даже могли бы подключить обработчик событий в событии загрузки элемента управления страница / пользователь.
Крис Манчини,
вы не создаете привязку к свойству "Binding" столбца таблицы данных.Ну, вы пишете "{Привязка User.UserName}", но это не создает привязку, потому что (как сказал Захари) столбец datagrid не наследуется от FrameworkElement и не имеет метода SetBinding .Таким образом, выражение "{Привязка User.UserName}" просто создает объект привязки и присваивает его свойству привязки столбца (это свойство является типом привязки).Затем столбец datagrid при генерации содержимого ячеек (метод, защищенный от GenerateElement) использует этот объект привязки для установки привязки к сгенерированным элементам (напримеро свойстве Text сгенерированного TextBlock), которые являются FrameworkElements
Решение GreatTall1 отличное, но его нужно немного изменить, чтобы оно заработало.
var n = d as Notifier;
if (n != null)
{
//Assign value in the callback will break the binding.
//n.MyVisibility = (Visibility)e.NewValue;
n.PropertyChanged(n, new PropertyChangedEventArgs("Visibility"));
}
Обратите внимание, что проблема не так проста, поскольку "Видимость" не является свойством зависимости.В DataGrid столбцы не являются частью визуального "дерева", поэтому вы не можете использовать AncestorType даже в WPF (или Silverlight 5).
Вот пара ссылок, связанных с WPF (пожалуйста, прокомментируйте, работает ли какая-либо из них для Silverlight - извините, у меня сейчас нет времени на тестирование)
Имеет действительно хорошее объяснение проблемы и неудач определенных решений (и умное решение):http://tomlev2.wordpress.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
И пара вопросов StackOverflow:
Это работает со столбцом шаблона таблицы данных:
public class ExtendedDataGridColumn : DataGridTemplateColumn
{
public static readonly DependencyProperty VisibilityProperty = DependencyProperty.Register("Visibility", typeof(Visibility), typeof(DataGridTemplateColumn), new PropertyMetadata(Visibility.Visible, VisibilityChanged));
public new Visibility Visibility
{
get { return (Visibility)GetValue(VisibilityProperty); }
set { SetValue(VisibilityProperty, value); }
}
private static void VisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if ((DataGridTemplateColumn)d != null)
{
((DataGridTemplateColumn)d).Visibility = (Visibility)e.NewValue;
}
}
}
Из вашего класса MyDataGridTextColumn вы могли бы получить окружающий DataGrid.Затем вы извлекаете свою ViewModel из DataContext DataGrid и добавляете обработчик к событию PropertyChanged вашей ViewModel.В обработчике вы просто проверяете имя свойства и его значение и соответствующим образом изменяете видимость столбца.Это не совсем лучшее решение, но оно должно сработать ;)