使用 x:Bind 通过 Converter 绑定到当前 DataContext
Binding to current DataContext with Converter using 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
传递给我的转换器。
{x:Bind}
使用生成的代码来实现其优势,并且在 {x:Bind}
中使用不同的 Path
时,生成的代码有一些差异。
这里我以一个简单的例子为例。如需完整示例,请查看 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);
}
}
当我们在MainPage.xaml
中使用{x:Bind Name, Converter={StaticResource ItemConvert}}
<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
,所以Update_(global::xBindWithConverter.Item obj, int phase)
方法中的obj
也是null
。
并且在XAML中使用{x:Bind 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((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
,所以你的 Convert
中的 value
是 null
最后它在 [=41= 抛出一个 NPE ].
但是如果我们在{x:Bind}
中使用另一个Path
,比如{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 的目的是提高性能,而转换器将显着影响代码的性能。您可以轻松地在您的模型中创建一个只读字段,并且当您的数据更改时,您可以引发 属性 changed 事件,并在那里转换数字。
例如,
[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
的函数,这开辟了新的机会:快速转换,而不会增加转换器的复杂性。参见 documentation.
我有以下转换器:
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
传递给我的转换器。
{x:Bind}
使用生成的代码来实现其优势,并且在 {x:Bind}
中使用不同的 Path
时,生成的代码有一些差异。
这里我以一个简单的例子为例。如需完整示例,请查看 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);
}
}
当我们在MainPage.xaml
中使用{x:Bind Name, Converter={StaticResource ItemConvert}}
<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
,所以Update_(global::xBindWithConverter.Item obj, int phase)
方法中的obj
也是null
。
并且在XAML中使用{x:Bind 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((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
,所以你的 Convert
中的 value
是 null
最后它在 [=41= 抛出一个 NPE ].
但是如果我们在{x:Bind}
中使用另一个Path
,比如{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 的目的是提高性能,而转换器将显着影响代码的性能。您可以轻松地在您的模型中创建一个只读字段,并且当您的数据更改时,您可以引发 属性 changed 事件,并在那里转换数字。
例如,
[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
的函数,这开辟了新的机会:快速转换,而不会增加转换器的复杂性。参见 documentation.