Привязка к текущему DataContext с помощью Converter с использованием x:Bind
У меня есть следующий конвертер:
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
Debug.WriteLine(value.GetType());
//The rest of the code
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
И XAML, который пытается использовать конвертер:
<ListView ItemsSource="{x:Bind StickersCVS.View}" >
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:StickerCategory">
<TextBlock Foreground="{x:Bind Converter={StaticResource MyConverter}}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Это дает мне NPE в value.GetType()
по-видимому, значение передается в null
,
Если я изменю следующую часть:
<TextBlock Foreground="{x:Bind Converter={StaticResource MyConverter}}"/>
в
<TextBlock Foreground="{Binding Converter={StaticResource MyConverter}}"/>
Тогда это работает. Debug
правильно выводит StickerCategory
как тип значения. Любая причина, почему x:Bind
проходит null
в конвертер и как мне заставить его работать с x:Bind
? Я пытаюсь пройти DataContext
к моему конвертеру.
2 ответа
{x:Bind}
использует сгенерированный код для достижения своих преимуществ и при использовании различных Path
в {x:Bind}
Сгенерированный код имеет некоторые отличия.
Здесь я использую простой пример для примера. Для полного образца, пожалуйста, проверьте на GitHub.
В примере у меня есть ViewModel, как показано ниже:
public class MyViewModel
{
public MyViewModel()
{
MyList = new List<Item>()
{
new Item {Name="1",Number=1 },
new Item {Name="2",Number=2 },
new Item {Name="3",Number=3 }
};
}
public List<Item> MyList { get; set; }
}
public class Item
{
public string Name { get; set; }
public int Number { get; set; }
public override string ToString()
{
return string.Format("Name: {0}, Number {1}", this.Name, this.Number);
}
}
Когда мы используем {x:Bind Name, Converter={StaticResource ItemConvert}}
в MainPage.xaml
<ListView ItemsSource="{x:Bind ViewModel.MyList}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Item">
<TextBlock Text="{x:Bind Converter={StaticResource ItemConvert}}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Он генерирует следующий код в MainPage.g.cs
public void DataContextChangedHandler(global::Windows.UI.Xaml.FrameworkElement sender, global::Windows.UI.Xaml.DataContextChangedEventArgs args)
{
global::xBindWithConverter.Item data = args.NewValue as global::xBindWithConverter.Item;
if (args.NewValue != null && data == null)
{
throw new global::System.ArgumentException("Incorrect type passed into template. Based on the x:DataType global::xBindWithConverter.Item was expected.");
}
this.SetDataRoot(data);
this.Update();
}
// IDataTemplateExtension
public bool ProcessBinding(uint phase)
{
throw new global::System.NotImplementedException();
}
public int ProcessBindings(global::Windows.UI.Xaml.Controls.ContainerContentChangingEventArgs args)
{
int nextPhase = -1;
switch(args.Phase)
{
case 0:
nextPhase = -1;
this.SetDataRoot(args.Item as global::xBindWithConverter.Item);
if (!removedDataContextHandler)
{
removedDataContextHandler = true;
((global::Windows.UI.Xaml.Controls.TextBlock)args.ItemContainer.ContentTemplateRoot).DataContextChanged -= this.DataContextChangedHandler;
}
this.initialized = true;
break;
}
this.Update_((global::xBindWithConverter.Item) args.Item, 1 << (int)args.Phase);
return nextPhase;
}
...
public void Update()
{
this.Update_(this.dataRoot, NOT_PHASED);
this.initialized = true;
}
А также
global::Windows.UI.Xaml.Controls.TextBlock element3 = (global::Windows.UI.Xaml.Controls.TextBlock)target;
MainPage_obj3_Bindings bindings = new MainPage_obj3_Bindings();
returnValue = bindings;
bindings.SetDataRoot((global::xBindWithConverter.Item) element3.DataContext);
bindings.SetConverterLookupRoot(this);
element3.DataContextChanged += bindings.DataContextChangedHandler;
global::Windows.UI.Xaml.DataTemplate.SetExtensionInstance(element3, bindings);
При инициализации страницы, element3.DataContextChanged += bindings.DataContextChangedHandler;
будет выполнен в первую очередь. После этого, DataContextChangedHandler
метод будет называться DataContextChanged
событие возникает при инициализации. И ProcessBindings
будет выполнен метод для обновления элемента контейнера элемента списка связанными данными.
в DataContextChangedHandler
метод, это вызывает this.Update();
метод, который вызывает Update_(global::xBindWithConverter.Item obj, int phase)
метод в конце. Но когда DataContextChangedHandler
метод называется, это args.NewValue
значение null
, Итак obj
в Update_(global::xBindWithConverter.Item obj, int phase)
метод также null
,
И при использовании {x:Bind Converter={StaticResource ItemConvert}}
в XAML сгенерированный код для Update_(global::xBindWithConverter.Item obj, int phase)
является:
// Update methods for each path node used in binding steps.
private void Update_(global::xBindWithConverter.Item obj, int phase)
{
if((phase & ((1 << 0) | NOT_PHASED )) != 0)
{
XamlBindingSetters.Set_Windows_UI_Xaml_Controls_TextBlock_Text(this.obj3.Target as global::Windows.UI.Xaml.Controls.TextBlock, (global::System.String)this.LookupConverter("ItemConvert").Convert(obj, typeof(global::System.String), null, null), null);
}
}
Как obj
является null
, Итак value
в вашем Convert
является null
и, наконец, он бросает NPE в value.GetType()
,
Но если мы используем другой Path
в {x:Bind}
лайк {x:Bind Name, Converter={StaticResource ItemConvert}}
сгенерированный код для Update_(global::xBindWithConverter.Item obj, int phase)
это отличается:
// Update methods for each path node used in binding steps.
private void Update_(global::xBindWithConverter.Item obj, int phase)
{
if (obj != null)
{
if ((phase & (NOT_PHASED | (1 << 0))) != 0)
{
this.Update_Name(obj.Name, phase);
}
}
}
private void Update_Name(global::System.String obj, int phase)
{
if((phase & ((1 << 0) | NOT_PHASED )) != 0)
{
XamlBindingSetters.Set_Windows_UI_Xaml_Controls_TextBlock_Text(this.obj3.Target as global::Windows.UI.Xaml.Controls.TextBlock, (global::System.String)this.LookupConverter("ItemConvert").Convert(obj, typeof(global::System.String), null, null), null);
}
}
Это будет определять, будет ли obj
является null
, Итак XamlBindingSetters.Set_Windows_UI_Xaml_Controls_TextBlock_Text
метод не будет вызываться здесь и NullReferenceException
не произойдет.
Чтобы решить проблему, как сказал @Markus Hütter, вы можете добавить null
проверьте в вашем конвертере как:
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value != null)
{
System.Diagnostics.Debug.WriteLine(value.GetType());
return value.ToString();
}
else
{
System.Diagnostics.Debug.WriteLine("value is null");
return null;
}
}
Я не думаю, что это хорошая идея использовать x:bind с конвертером в целом, цель использования x:bing - повысить производительность, в то время как конвертер сильно повлияет на производительность кода. вы можете легко создать поле "только для чтения" в своей модели, а когда ваши данные будут изменены, вы можете вызвать событие свойства изменено и преобразовать число там.
Например,
[JsonIgnore]
public double IsUnreadOpacity
{
get { return (IsUnread) ? 1 : 0; }
}
public bool IsUnread
{
get { return _isUnread; }
set
{
if (value == _isUnread)
return;
Set(ref _isUnread, value);
RaisePropertyChanged(nameof(IsUnreadOpacity));
}
}
Однако, начиная с версии 1607, вы можете использовать функции с
x:Bind
и это открывает новые возможности: быстрое преобразование без дополнительной сложности преобразователей. См. Документацию.