Я борюсь с многоразовым и относительно простым UserControl.
Мои данные хранятся в классах, которые реализуют простой интерфейс IHasValue:
public interface IHasValue<T>
{
T Value { get; }
ValueType Type { get; }
}
Мне нужен многоразовый элемент управления для редактирования данные в нескольких местах вокруг приложения. Это элемент управления, который у меня есть на данный момент, он просто показывает кнопку со знаком плюс, когда SelectedValue имеет значение NULL, щелчок по нему устанавливает SelectedValue (я добавлю пользовательский ввод позже) и показывает значение вместо кнопки:
<UserControl x:Class="DotsCompanion.Controls.HasValueFloatSelectorControl"
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:DotsCompanion.Controls"
xmlns:conv="clr-namespace:DotsCompanion.Converters"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<conv:NullToTrueFalseConverter x:Key="NullToTrueFalseConverter" />
<conv:MyDebugConverter x:Key="MyDebugConverter" />
<DataTemplate x:Key="EmptyValueTemplate" DataType="{x:Type ContentControl}">
<Button Click="NewValueClick">
<TextBlock Text="+"></TextBlock>
</Button>
</DataTemplate>
<DataTemplate x:Key="HasValueTemplate" DataType="{x:Type ContentControl}">
<StackPanel DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type ContentControl}}}">
<!-- THIS IS THE PROBLEMATIC BINDING -->
<TextBlock Text="{Binding Path=SelectedValue, Converter={StaticResource MyDebugConverter}}"></TextBlock>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid Name="LayoutRoot">
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedValue, Converter={StaticResource NullToTrueFalseConverter}}" Value="true">
<Setter Property="ContentTemplate" Value="{DynamicResource HasValueTemplate}"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedValue, Converter={StaticResource NullToTrueFalseConverter}}" Value="false">
<Setter Property="ContentTemplate" Value="{DynamicResource EmptyValueTemplate}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</UserControl>
Это чрезвычайно простой код для элемента управления:
public partial class HasValueFloatSelectorControl : UserControl
{
public IHasValue<float> SelectedValue
{
get { return (IHasValue<float>)GetValue(SelectedValueProperty); }
set { SetValue(SelectedValueProperty, value); }
}
public static readonly DependencyProperty SelectedValueProperty =
DependencyProperty.Register("SelectedValue", typeof(IHasValue<float>), typeof(HasValueFloatSelectorControl), new PropertyMetadata(default(IHasValue<float>)));
public HasValueFloatSelectorControl()
{
InitializeComponent();
LayoutRoot.DataContext = this;
}
private void NewValueClick(object sender, EventArgs e)
{
SelectedValue = new FloatPrimitive(2.0f);
}
}
FloatPrimitive - это класс, в котором фактически хранятся данные (с плавающей точкой), он реализует IHasValue, а также переопределяет ToString (), поэтому что float отображается в виде строки.
Проблема в том, что привязка Text к SelectedValue, похоже, работает только при использовании конвертера. В приведенном выше коде MyDebugConverter просто возвращает значение как есть:
// This should be literally useless AFAIK
public class MyDebugConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Если я удаляю конвертер, привязка просто ничего не показывает. Согласно трассировке выходного окна, DataItem имеет значение null, но с помощью проводника динамических свойств я вижу, что DataContext правильно установлен, а SelectedValue действительно является FloatPrimitive.
Я пытался найти решение, но до сих пор Я нашел только противоположные случаи (привязка не работает с конвертером). Я довольно новичок в WPF, поэтому я не уверен, с чего начать отладку, так как это кажется довольно необычной ситуацией, и онлайн-ресурсы на данный момент не помогают.
Что я пробовал:
- привязка к SelectedValue за пределами DataTemplate путем добавления простого TextBlock внутри сетки Root, но я получил тот же результат (необходим конвертер);
- setting SelectedValue путем привязки к нему извне элемента управления и вызова метода NewValueClick () внутри элемента управления;
- добавление вызова Debugger.Break () внутри MyDebugConverter, который позволяет мне проверить, что значение не равно нулю, и корректно устанавливается, но с другой стороны, использование конвертера - это то, что позволяет ему правильно отображаться в первую очередь;
- много возни с проводником текущих свойств
Может это связано с тем, что я использую интерфейс? У меня есть очень похожая настройка для другого пользовательского элемента управления в проекте, который прекрасно работает, но он использует абстрактный класс + наследование в отличие от интерфейса + реализация.