У меня есть класс с индексом, индексированный значением enum
. Мне удалось отобразить значения для этого класса в ItemsControl
, установив сами значения enum
как ItemsSource
для управления, предоставив для элементов в элементе управления, а затем привязывая соответствующее свойство управления с помощью MultiBinding
для выполнения поиск индексированного значения.Как создать привязку к косвенно указанному свойству, но который автоматически обновляется?
К сожалению, поскольку два источника Binding
s из MultiBinding
являются экземпляром класса и значением индекса, которые сами никогда не изменяются, отображаемое значение не обновляется при изменении индексированного значения. Связывание не знает данных, которые фактически изменяются, а только части данных, которые приводят к нему. :(
Я был в состоянии работать вокруг этого явно найти элемент шаблона для данного enum
значения, извлекая его MultiBindingExpression
, и называя UpdateTarget()
по этому вопросу. Но это очень неудовлетворительно, не в последнюю очередь причина в том, что для что для работы, код обновления значения должен знать интимные подробности того, как данные были представлены в XAML
Вот небольшой пример, иллюстрирующий то, что я делаю & hellip;
XAML:.
<Window x:Class="TestMultiBindingUpdate.MainWindow"
x:ClassModifier="internal"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestMultiBindingUpdate"
xmlns:system="clr-namespace:System;assembly=mscorlib"
x:Name="root"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<ObjectDataProvider x:Key="PropertyNameValues"
MethodName="GetValues"
ObjectType="{x:Type system:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type Type="{x:Type local:PropertyName}"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<StackPanel>
<ListBox x:Name="listBox1"
ItemsSource="{Binding Source={StaticResource PropertyNameValues}}"
Margin="5">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}"/>
<TextBlock Text=": "/>
<TextBlock>
<TextBlock.Text>
<MultiBinding>
<MultiBinding.Converter>
<local:PropertyNameToValueConverter/>
</MultiBinding.Converter>
<Binding ElementName="root"/>
<Binding/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<StackPanel Orientation="Horizontal">
<ComboBox x:Name="comboBox1"
ItemsSource="{Binding Source={StaticResource PropertyNameValues}}"
SelectedValue="{x:Static local:PropertyName.A}"
Margin="5" Width="50"/>
<Button Content="Increment"
HorizontalAlignment="Center" VerticalAlignment="Center"
Click="Button_Click"/>
</StackPanel>
<ListBox>
<TextBlock Text="{Binding ElementName=root, Path=A, StringFormat=A: {0}}"/>
<TextBlock Text="{Binding ElementName=root, Path=B, StringFormat=B: {0}}"/>
<TextBlock Text="{Binding ElementName=root, Path=C, StringFormat=C: {0}}"/>
</ListBox>
</StackPanel>
</Window>
Примечание: в целях иллюстрации я скопировал индексированные значения в качестве реальных свойств и добавил ListBox
, который связывается непосредственно с этими свойствами. Это, очевидно, второй альтернативный способ решить эту проблему, и он решает проблему кода, который так много знает о XAML. Но это означает, что XAML должен быть создан более утомительно, кодируя каждое значение enum
отдельно, а не используя возможности шаблонов.
C#:
enum PropertyName
{
A,
B,
C
}
class PropertyNameToValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
MainWindow window = (MainWindow)values[0];
PropertyName propertyName = (PropertyName)values[1];
return window[propertyName].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
partial class MainWindow : Window
{
public static readonly DependencyProperty AProperty =
DependencyProperty.Register("A", typeof(int), typeof(MainWindow), new PropertyMetadata(0));
public static readonly DependencyProperty BProperty =
DependencyProperty.Register("B", typeof(int), typeof(MainWindow), new PropertyMetadata(0));
public static readonly DependencyProperty CProperty =
DependencyProperty.Register("C", typeof(int), typeof(MainWindow), new PropertyMetadata(0));
public int A
{
get { return (int)GetValue(AProperty); }
set { SetValue(AProperty, value); }
}
public int B
{
get { return (int)GetValue(BProperty); }
set { SetValue(BProperty, value); }
}
public int C
{
get { return (int)GetValue(CProperty); }
set { SetValue(CProperty, value); }
}
public int this[PropertyName index]
{
get
{
switch (index)
{
case PropertyName.A: return A;
case PropertyName.B: return B;
case PropertyName.C: return C;
default: throw new ArgumentException();
}
}
set
{
switch (index)
{
case PropertyName.A: A = value; break;
case PropertyName.B: B = value; break;
case PropertyName.C: C = value; break;
default: throw new ArgumentException();
}
RefreshBinding(index);
}
}
private void RefreshBinding(PropertyName index)
{
ListBoxItem listBoxItem = (ListBoxItem)listBox1.ItemContainerGenerator.ContainerFromItem(index);
StackPanel itemStackPanel = Util.FindFirstVisualChildOfType<StackPanel>(listBoxItem);
TextBlock textBlock = (TextBlock)itemStackPanel.Children[2];
BindingOperations.GetMultiBindingExpression(textBlock, TextBlock.TextProperty).UpdateTarget();
}
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
this[(PropertyName)comboBox1.SelectedValue]++;
}
}
static class Util
{
public static T FindFirstVisualChildOfType<T>(DependencyObject o) where T : DependencyObject
{
if (o != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(o); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(o, i);
if (child != null)
{
T t = child as T;
if (t != null)
{
return t;
}
t = FindFirstVisualChildOfType<T>(child);
if (t != null)
{
return t;
}
}
}
}
return null;
}
}
Я бы очень признателен за решение этой проблемы, которая отвечает следующим критериям:
- Не требует ручного кодирования отдельных элементов XAML для каждого
enum
значения , - Не требует кода, чтобы знать, как структурируется XAML.
- Не требует наличия отдельного свойства в классе для каждого значения
enum
.
Я думаю, что толчок приходит в засуху, критерии № 3 выше договорные. Если это все Я должен был сделать, чтобы это работало лучше, это было бы не так плохо, как другие проблемы. Но я бы предпочел чисто гибкое автоматическое решение.
бонус вопрос: Поскольку мой IMultiValueConverter
объект вызывается во время разработки с неправильным видом объекта, я получаю следующее исключение брошено в преобразователе на экспрессию (MainWindow)values[0]
:
Невозможно для создания объекта типа «Microsoft.Expression.WpfPlatform.InstanceBuilders.WindowInstance» для ввода «TestMultiBindingUpdate.MainWindow».
Я могу обойти это, изменив конвертер, чтобы проверить тип и просто вернуться, например. Binding.DoNothing
если не MainWindow
type ожидает. Но тогда конвертер не работает во время разработки. Мне интересно, есть ли способ гарантировать, что во время разработки он использует тип объекта, который ожидает мой конвертер.
Я также приветствовал бы любые советы по этому поводу, если кто-то хочет предложить какие-либо.
Если я правильно понял ваше решение, но метод 'RefreshBinding' зависит от пользовательского интерфейса. Значит, вы хотите сделать его независимым от пользовательского интерфейса? Или я неправильно понял? –
@SriramSakthivel: да. Я не возражаю, чтобы называть дополнительный код в установщике индексатора, но я бы предпочел, чтобы он не был так тесно связан с фактическим используемым XAML. То есть Я должен иметь возможность изменять «DataTemplate» или даже связывать индексированное значение с каким-либо другим произвольным элементом пользовательского интерфейса, а не возвращаться и исправлять код, чтобы это учесть. –