Я посреди тестирования пользовательского элемента управления, который я создал, и я встречаюсь с чем-то, что это необъяснимо мне.
Управление расширение ComboBox, который обрабатывает значения определенного пользовательского типа. Это имеет свойство зависимости того пользовательского типа, который является целевым свойством Привязки.
У меня есть оператор трассировки в методе set, и я вижу, что свойство становится установленным. Но это не появляется в моем пользовательском элементе управления.
Теперь, обычно я говорил бы, хорошо, у меня есть ошибка в моем пользовательском элементе управления. Я, вероятно, делаю, хотя я экранирован об этом. Но этот вопрос не о нахождении ошибки в моем управлении. Продолжайте читать; вот то, где это становится странным.
Я также использую небольшой преобразователь значения Bea Stollnitz, чтобы помочь отладить Привязку:
public class DebuggingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value; // Add the breakpoint here!!
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException("This method should never be called");
}
}
Идея позади этого состоит в том, что я добавляю этот преобразователь к своей Привязке и могу установить точку останова для наблюдения, какое значение выставляется к цели. Хорошо, это работает просто великолепно. Я вижу, что значение выставляется.
На самом деле это работает немного слишком прекрасное. Если DebuggingConverter присоединен к Привязке, пользовательский элемент управления отображает значение. Если это не, это не делает.
Как это даже возможно? Как мог преобразователь значения, который делает ничто не влияет на поведение связанного элемента управления?
Править:
Не то, чтобы это, вероятно, поможет, но здесь является XAML для пользовательского элемента управления:
<a:CodeLookupBox
Grid.Column="1"
Grid.IsSharedSizeScope="True"
MinWidth="100"
Style="{Binding Style}">
<a:CodeLookupBox.CodeLookupTable>
<Binding Path="Codes" Mode="OneWay"/>
</a:CodeLookupBox.CodeLookupTable>
<a:CodeLookupBox.SelectedCode>
<Binding Path="Value" Mode="TwoWay" ValidatesOnDataErrors="True"/>
</a:CodeLookupBox.SelectedCode>
</a:CodeLookupBox>
Без преобразователя на второй привязке ведет себя управление, как будто я не установил SelectedCode
. Даже при том, что оператор трассировки в OnSelectedCodePropertyChanged
обработчик показывает это e.Value
действительно содержит правильное значение. Это происходит независимо от того, присоединил ли преобразователь или нет.
Я пытался перепроектировать эту проблему с мысленным экспериментом: если Вы хотели создать связанный пользовательский элемент управления, поведение которого, измененное, если бы никакой-op преобразователь не был присоединен к своей привязке, как Вы сделали бы это? Я не знаю достаточно о привязке для предложения ответа.
Хорошая новость: я знаю, почему SelectedCode
не устанавливается, когда я не использую конвертер значений. Плохая новость заключается в том, что у меня все еще есть что-то вроде загадки, но проблема была немного продвинута вверх по пищевой цепочке, и у меня есть обходной путь.
Этот элемент управления по сути является сильно типизированным комбобоксом с кучей дополнительных возможностей, которые становятся возможными благодаря тому, что он знает, какие элементы в нем находятся. Свойства SelectedCode
и CodeLookupTable
сильно типизированы, и они скрывают основные свойства SelectedItem
и ItemsSource
, которые таковыми не являются. (Вот, кстати, почему это пользовательский элемент управления, а не подкласс ComboBox
; я не хочу, чтобы эти свойства были видны, потому что при их неправильной установке может произойти много чего, и ничего хорошего.)
Вот что происходит. Вот мой отладочный вывод, когда подключен конвертер значений (число - это хэш-код элемента управления, потому что у меня их куча, и все они рисуются одновременно при инициализации программы):
14626603: OnCodeLookupTablePropertyChanged
CodeLookupTable property set to Proceedings.Model.CodeLookupTable
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
14626603: OnSelectedCodePropertyChanged:
SelectedCode property set to Unlicensed Driver [VC12500(A)]
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
Это ожидаемое поведение. Свойство CodeLookupTable
установлено, поэтому установка SelectedCode
на один из элементов этой коллекции правильно устанавливает SelectedItem
на базовом ComboBox
.
Но без конвертера значений мы получаем следующее:
16143157: OnSelectedCodePropertyChanged:
SelectedCode property set to Unlicensed Driver [VC12500(A)]
box.MainComboBox.ItemsSource =
16143157: OnCodeLookupTablePropertyChanged
CodeLookupTable property set to Proceedings.Model.CodeLookupTable
box.MainComboBox.ItemsSource = MS.Internal.Data.EnumerableCollectionView
Здесь свойство SelectedCode
устанавливается раньше, чем свойство CodeLookupTable
. Поэтому, когда метод пытается установить SelectedItem
на нижележащем ComboBox
, ничего не происходит, потому что ItemsSource
равен null.
И здесь кроется корень проблемы. Я по глупости предположил, что порядок, в котором привязки обновляют свою цель, совпадает с порядком, в котором они объявлены в XAML. (Одна из причин, по которой я объявил привязки в виде элементов, а не атрибутов, заключается в том, что порядок элементов в XML-документе детерминирован, а порядок атрибутов - нет. Не то чтобы я об этом не подумал.) Очевидно, это не так.
Я также предполагал, может быть, не так глупо, что порядок, в котором привязки обновляют свою цель, не зависит от того, есть ли у них подключенные преобразователи значений. Что ж, зависит. Интересно, от чего еще это зависит?
К счастью, у меня есть способ обойти это. Поскольку мой объект CodeLookup
содержит ссылку на CodeLookupTable
, я могу сделать так, чтобы сеттер SelectedCode
сначала устанавливал свойство CodeLookupTable
(и, соответственно, ItemsSource
), если оно еще не установлено. Это позволит избавиться от проблемы без необходимости вставлять в привязку поддельный преобразователь значений и надеяться, что поведение привязок никогда не изменится.
Редактировать
Вот как выглядят объявления свойств:
#region SelectedCode
public static readonly DependencyProperty SelectedCodeProperty = DependencyProperty.Register(
"SelectedCode", typeof(CodeLookup), typeof(CodeLookupBox),
new FrameworkPropertyMetadata(OnSelectedCodePropertyChanged));
private static void OnSelectedCodePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
CodeLookupBox box = (CodeLookupBox)source;
CodeLookup code = e.NewValue as CodeLookup;
// this right here is the fix to the original problem:
if (box.CodeLookupTable == null && code != null)
{
box.CodeLookupTable = code.Table;
}
box.MainComboBox.SelectedItem = e.NewValue;
}
public CodeLookup SelectedCode
{
get { return GetValue(SelectedCodeProperty) as CodeLookup; }
set { SetValue(SelectedCodeProperty, value); }
}
#endregion
#region CodeLookupTable
public static readonly DependencyProperty CodeLookupTableProperty = DependencyProperty.Register(
"CodeLookupTable", typeof(CodeLookupTable), typeof(CodeLookupBox),
new FrameworkPropertyMetadata(OnCodeLookupTablePropertyChanged));
private static void OnCodeLookupTablePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
CodeLookupBox box = (CodeLookupBox)source;
CodeLookupTable table = (CodeLookupTable)e.NewValue;
box.ViewSource = new CollectionViewSource { Source = table.Codes };
box.View = box.ViewSource.View;
box.MainComboBox.ItemsSource = box.View;
}
public CodeLookupTable CodeLookupTable
{
get { return GetValue(CodeLookupTableProperty) as CodeLookupTable; }
set { SetValue(CodeLookupTableProperty, value); }
}
#endregion
Хм... очень странно. Я видел подобное поведение при работе с MultiBindings с несколькими конвертерами, но не на простой привязке. Из любопытства, как вы определяете свои DP на контроле? (включая обратные вызовы, параметры метаданных и т.д.)
Некоторые вещи, которые можно попробовать (после удаления крючка конвертера):
попробуйте реализовать с функцией ConvertBack вы также используете двустороннюю привязку, поэтому проблема может заключаться в том, что исключение будет проигнорировано «xaml», но когда вы перейдете в режим отладки, вы, возможно, остановите операцию «отправить значение обратно»?
Что делает установка SelectedCode? Можете ли вы выложить код обработчика изменения свойства?
Вы предположили, что отладка показывает, что свойство устанавливается в правильное значение, поэтому наиболее очевидным предположением является то, что код, обеспечивающий предполагаемое поведение, некорректен.