【问题标题】:How do I use x:Bind function binding with a null parameter?如何使用带有 null 参数的 x:Bind 函数绑定?
【发布时间】:2020-06-30 20:24:09
【问题描述】:

我正在尝试格式化对象的多个属性,并使用x:Bind 函数绑定将结果绑定到TextBlock。绑定如下所示:

<TextBlock Text="{x:Bind local:MainViewModel.FormatWidget(ViewModel.SelectedItem), Mode=OneWay}" />

只要对象不是null,就可以完美运行。但是,当对象为 null 时,我的函数不会被调用。或者更准确地说,如果对象最初为null,则调用函数,但如果对象稍后变为null,则该函数被调用。

为什么当参数为空时函数没有被调用,我该如何使用它?

这是一个复制品。当您运行它时,请注意该函数最初正确绑定到 null SelectedItem 并显示“未选择小部件”。但是当您选择一个项目然后取消选择它(CTRL + 单击 取消选择),它不会调用该函数并显示FallbackValue。 (如果没有设置FallbackValue,则根本不会更新绑定。)

MainPage.xaml

<Page
    x:Class="NullFunctionBindingParameter.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:NullFunctionBindingParameter">
    <Page.Resources>
        <Style TargetType="TextBlock">
            <Setter Property="Margin" Value="20" />
        </Style>
    </Page.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <ListView
            Grid.Column="0"
            ItemsSource="{x:Bind ViewModel.Widgets, Mode=OneWay}"
            SelectedItem="{x:Bind ViewModel.SelectedItem, Mode=TwoWay}">
            <ListView.ItemTemplate>
                <DataTemplate x:DataType="local:Widget">
                    <TextBlock Text="{x:Bind Name}" />
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <TextBlock Grid.Column="1" Text="{x:Bind local:MainViewModel.FormatWidget(ViewModel.SelectedItem), Mode=OneWay, FallbackValue=MyFallbackValue}" />
    </Grid>
</Page>

MainPage.xaml.cs

using Windows.UI.Xaml.Controls;

namespace NullFunctionBindingParameter
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            InitializeComponent();
        }

        public MainViewModel ViewModel { get; } = new MainViewModel();
    }
}

MainViewModel.cs

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace NullFunctionBindingParameter
{
    public class MainViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private Widget _selectedItem;

        public Widget SelectedItem
        {
            get => _selectedItem;
            set
            {
                if (_selectedItem != value)
                {
                    _selectedItem = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
                }
            }
        }

        public ObservableCollection<Widget> Widgets { get; } = new ObservableCollection<Widget>()
        {
            new Widget
            {
                Id = Guid.NewGuid(),
                Name = "Regular Widget",
                Model = "WX2020-01",
                Description = "Your typical everyday widget."
            },
            new Widget
            {
                Id = Guid.NewGuid(),
                Name = "Super Widget",
                Model = "WX2020-02",
                Description = "An extra special upgraded widget."
            },
            new Widget
            {
                Id = Guid.NewGuid(),
                Name = "Broken Widget",
                Model = "WX2020-03",
                Description = "A widget that has been used and abused."
            },
            new Widget
            {
                Id = Guid.NewGuid(),
                Name = "Fake Widget",
                Model = "WX2020-04",
                Description = "It's not really a widget at all!"
            },
            new Widget
            {
                Id = Guid.NewGuid(),
                Name = "Surprise Widget",
                Model = "WX2020-05",
                Description = "What kind of widget will it be?"
            },
            new Widget
            {
                Id = Guid.NewGuid(),
                Name = "Invisible Widget",
                Model = "WX2020-06",
                Description = "Our most inexpensive widget."
            },
            new Widget
            {
                Id = Guid.NewGuid(),
                Name = "Backwards Widget",
                Model = "WX2020-07",
                Description = "Really more of a tegdiw, come to think of it."
            }
        };

        public static string FormatWidget(Widget widget)
        {
            if (widget == null)
                return "No widget selected";
            else
                return $"{widget.Name} [{widget.Model}] {widget.Description}";
        }

        public string GetFormattedWidget()
        {
            return FormatWidget(SelectedItem);
        }
    }
}

Widget.cs

using System;
using System.ComponentModel;

namespace NullFunctionBindingParameter
{
    public class Widget : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private Guid _id;
        private string _name;
        private string _model;
        private string _description;

        public Guid Id
        {
            get => _id;
            set
            {
                if (_id != value)
                {
                    _id = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Id)));
                }
            }
        }

        public string Name
        {
            get => _name;
            set
            {
                if (_name != value)
                {
                    _name = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
                }
            }
        }

        public string Model
        {
            get => _model;
            set
            {
                if (_model != value)
                {
                    _model = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Model)));
                }
            }
        }

        public string Description
        {
            get => _description;
            set
            {
                if (_description != value)
                {
                    _description = value;
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Description)));
                }
            }
        }
    }
}

【问题讨论】:

  • 这是在 GitHub 上作为issue 添加的。

标签: c# xaml data-binding uwp xbind


【解决方案1】:

在这种情况下,我建议您使用Converter,而不是直接在绑定语句中使用静态方法。

小部件转换器

public class WidgetConverter:IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        var widget = value as Widget;
        return MainViewModel.FormatWidget(widget);
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

用法

<Page.Resources>
    ...
    <local:WidgetConverter x:Key="WidgetConverter"/>
</Page.Resources>

...

<TextBlock
    Grid.Row="2"
    Grid.Column="2"
    Text="{x:Bind ViewModel.SelectedItem, Mode=OneWay,Converter={StaticResource WidgetConverter}}"/>

最好的问候。

【讨论】:

  • 感谢您的建议。当然,这将是获得相同输出的有效方法;但是,我真的很喜欢 x:Bind function binding 的易用性和可读性,并希望有一个不需要回退到转换器的解决方案。
  • 目前没有好的解决方案。函数绑定内部可能存在拦截机制,阻止使用null进行计算。转换器没有这个问题,是推荐的方法
  • @RichardZhangMSFT 根据this,鼓励使用函数来格式化和转换值:“提示您可以使用 x:Bind 中的函数来实现与通过 Converters 和WPF 中的多重绑定。” this 说,“{x:Bind} 使绑定路径中的最后一步成为一个函数。这可用于执行转换,并执行依赖于多个属性的绑定。”
  • 嗨,也许这是绑定设计的疏忽。 Converter 可以作为临时解决方案来解决您的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-06-13
  • 1970-01-01
  • 1970-01-01
  • 2014-03-02
  • 1970-01-01
  • 2020-09-25
  • 2015-05-11
相关资源
最近更新 更多