如何使用转换器与绑定一起使用属性变为事件



我有一个 ListBox绑定到对象列表,对于 ListBox中的每个项目,有一个带有转换器的 TextBlock,但是当对象的属性更改时,我无法获得如果不使用转换器绑定,则可以更新这些TextBlock

窗口的Xmal如下:

<Window x:Class="BindingPropertyChanged.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:BindingPropertyChanged"
        mc:Ignorable="d"
        Title="MainWindow" Height="400" Width="600">
    <Window.Resources>
        <local:LocationToTextConverter x:Key="LocationToTextConverter" />
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="2*"/>
        </Grid.ColumnDefinitions>
        <ListBox
            Grid.Row="2"
            HorizontalContentAlignment="Stretch"
            ItemsSource="{Binding Items}"
            SelectedItem="{Binding SelectedItem}"
          >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="auto"/>
                            <RowDefinition Height="auto"/>
                        </Grid.RowDefinitions>
                        <TextBlock x:Name="NameText" 
                            Text="{Binding Converter={StaticResource LocationToTextConverter}, UpdateSourceTrigger=PropertyChanged}" />
                        <TextBlock x:Name="DescriptionText"
                            Grid.Row="1" Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" />
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <Grid Grid.Column="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>
            <Label HorizontalAlignment="Right" Content="Place" />
            <ComboBox Grid.Column="1"
                ItemsSource="{Binding Places}"
                SelectedValue="{Binding SelectedItem.PlaceId}"
                SelectedValuePath="Id"
                DisplayMemberPath="Name"
                IsEditable="False"/>
            <Label Grid.Row="1" HorizontalAlignment="Right" Content="Description" />
            <TextBox Grid.Row="1" Grid.Column="1"
                Text="{Binding SelectedItem.Description, 
                    UpdateSourceTrigger=PropertyChanged, 
                    ValidatesOnNotifyDataErrors=True}"
                VerticalAlignment="Center"/>
        </Grid>
    </Grid>
</Window>

左侧是上述ListBox,右侧是TextBoxComboBox,用于在ListBox中创作所选项目。在ListBox.ItemTemplate中,如果我在右侧更改TextBox的文本,而NameText无法更新,则可以更新DescriptionText,因为它在绑定中使用了转换器。

以下代码是域模型的类:

public class DomainBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberNameAttribute] string propertyName = "None")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
public class Location : DomainBase
{
    private int _placeId;
    private string _description;
    public int PlaceId
    {
        get { return _placeId; }
        set
        {
            if (_placeId == value) return;
            _placeId = value;
            OnPropertyChanged();
        }
    }
    public string Description
    {
        get { return _description; }
        set
        {
            if (_description == value) return;
            _description = value;
            OnPropertyChanged();
        }
    }
}
public class Place : DomainBase
{
    public int Id { get; set; }
    public string Name { get; set; }
}

有一个用于虚拟数据的Repository类:

public class Repository
{
    public static ICollection<Place> _places;
    public static ICollection<Place> Places
    {
        get { return _places; }
    }
    public static ICollection<Location> _locations;
    public static ICollection<Location> Locations
    {
        get { return _locations; }
    }
    static Repository()
    {
        _places = new List<Place>();
        _places.Add(new Place() { Id = 1, Name = "Downtown Center" });
        _places.Add(new Place() { Id = 2, Name = "Headquarter" });
        _locations = new List<Location>();
        _locations.Add(new Location() { PlaceId = 1, Description = "Room 101" });
        _locations.Add(new Location() { PlaceId = 2, Description = "B06" });
    }
}

ViewModel类设置为窗口的DataContext

public class ViewModel : INotifyPropertyChanged
{
    private ObservableCollection<Location> _items = new ObservableCollection<Location>(Repository.Locations);
    private Location _selectedItem;
    public ObservableCollection<Location> Items
    {
        get
        {
            return _items;
        }
        private set
        {
            if (_items == value) return;
            _items = value;
            OnPropertyChanged();
            OnPropertyChanged("SelectedItem");
        }
    }
    public Location SelectedItem
    {
        get
        {
            return _selectedItem;
        }
        set
        {
            if (_selectedItem == value) return;
            _selectedItem = value;
            OnPropertyChanged(); 
        }
    }
    public ObservableCollection<Place> Places
    {
        get
        {
            return new ObservableCollection<Place>(Repository.Places);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged([CallerMemberName] string propertyName = "None")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

最后,转换器:

class LocationToTextConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string result = "";
        Location location = value as Location;
        if (location != null)
        {
            Place place = Repository.Places.Single(o => o.Id == location.PlaceId);
            if (place != null)
            {
                string placeName = place.Name;
                if (!String.IsNullOrWhiteSpace(placeName))
                {
                    result = placeName + ", ";
                }
            }
            result += location.Description;
        }
        return result;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

我想要的是在ListBox中使用Location'S Description显示PlaceName,当我更改所选LocationDescription时,ListBox中的显示名称也应立即更改。有人知道如何实现吗?

您的问题在这里

                    <TextBlock x:Name="NameText" 
                        Text="{Binding Converter={StaticResource LocationToTextConverter}, UpdateSourceTrigger=PropertyChanged}" />

在您的绑定中,您没有指定路径。当您执行此操作时,伴随的项目将是一个位置对象。由于您正在更改位置对象的描述属性,而不是位置对象本身,因此TextBlock不会更改属性通知。

您只需要更改几行代码,而是使用多键。

<TextBlock.Text>
    <MultiBinding>
        <Binding />
        <Binding Path=Description />
        //dont forget to specify the converter
....
class LocationToTextConverter : IMultiValueConverter
{
    //in the Convert method set, ignore value[1] and change location to values[0]
    Location location = values[0] as Location;

现在,您的转换器应随时随时更改描述属性。

问题是Location未更改,因此未调用NotifyPropertyChanged。最简单的解决方案是在Location上创建属性。因此,有两个选择可以做到:

  • Location调用OnPropertyChanged("Location"),以便触发它
  • Location上添加自己的属性,并绑定到此属性而不是Location

为什么不拥有属性并与之绑定?

public class Location : DomainBase
{
    private int _placeId;
    private string _description;
    public int PlaceId
    {
        get { return _placeId; }
        set
        {
            if (_placeId == value) return;
            _placeId = value;
            OnPropertyChanged();
        }
    }
    public string Description
    {
        get { return _description; }
        set
        {
            if (_description == value) return;
            _description = value;
            OnPropertyChanged();  // make sure  this Notify RepPlusDescription
        }
    }
    public string RepPlusDescription
    {
        get 
        {   
            string result;
            Place place = Repository.Places.FirstOrDefault(o => o.Id == placeId);
            if (place != null)
            {
                string placeName = place.Name;
                if (!String.IsNullOrWhiteSpace(placeName))
                {
                    result = placeName + ", ";
                }
            }
            return result += location.Description; 
        }
     }
}

最新更新