使用x绑定到使用Converter的当前DataContext:绑定

时间:2015-11-13 04:51:29

标签: c# listview uwp xbind

我有以下转换器:

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>

这给了我一个value.GetType()的NPE,显然传入的值是null

如果我更改以下部分:

<TextBlock Foreground="{x:Bind Converter={StaticResource MyConverter}}"/>

<TextBlock Foreground="{Binding Converter={StaticResource MyConverter}}"/>

然后它有效。 Debug正确输出StickerCategory作为值的类型。 x:Bindnull传递到转换器的任何原因以及如何使其与x:Bind一起使用?我试图将DataContext传递给我的转换器。

3 个答案:

答案 0 :(得分: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);
    }
}

我们在 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,因此obj方法中的Update_(global::xBindWithConverter.Item obj, int phase)也为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);
    }
}

由于objnull,因此value中的Convertnull,最后会在value.GetType()处抛出NPE。

但是,如果我们在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不会发生。

要解决这个问题,就像@MarkusHü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;
    }
}

答案 1 :(得分:1)

我不认为一般使用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));
        }
    }

答案 2 :(得分:0)

但是,从版本1607开始,您可以将函数与x:Bind一起使用,这带来了新的机遇:快速转换而无需增加转换器的复杂性。参见documentation.