如何在WPF中用层次结构中的上游值进行绑定?

问题描述 投票:0回答:1

我有一个项目列表,我希望在一个项目列表中显示。ListView/GridView. 每个项目都是一个包含格式和字节数组的类对象,格式决定了字节的显示方式(十六进制或十进制)。 格式决定了字节的显示方式(十六进制或十进制)。 我使用了一个转换器来回切换到 TextBox.Text 和字节数组。

转换器需要格式和字符串数组。 我试着用 IValueConverter 并将格式作为一个 ConverterParameter但这并不奏效,因为它不是一个 DependencyProperty. 我试着用 IMultiValueConverter 但这并不奏效,因为我没有得到的格式在 ConvertBack. 我想,如果我可以绑定到整个对象(MyDataItem),那么转换器就可以正常工作。 然而,我不知道如何绑定到这个对象。 我尝试了一系列的变化,使用 RelativeSource 和其他属性,但是想不通。 谁能帮我解决绑定问题?

如果有更好的方法来完成我的任务,欢迎提出来。

public enum FormatEnum
{
    Decimal,
    Hex
}

public class MyDataItem
{
    public byte[] Data { get; set; }

    public FormatEnum Format { get; set; }
}

public class ViewModel
{
    ObservableCollection<MyDataItem> DataItems = new ObservableCollection<MyDataItem>();
}

XAML(含非工作绑定)

<ListView ItemsSource="{Binding DataItems}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Format">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <!--ComboBox for the format-->
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>

            <GridViewColumn Header="Data">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Text="{Binding Path,
                                        Converter={StaticResource ResourceKey=DataBytesConverter}, 
                                        ConverterParameter={Binding Format}}"/>                        
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>
wpf binding ivalueconverter
1个回答
0
投票

欢迎来到SO!

欢迎来到SO.S.O.!只是为将来提个醒......如果你提供了一个问题,你会有更大的机会得到回答。MCVE. 你让人们为重现你的确切问题所做的工作越多,他们就越不愿意这样做。

有几种不同的方法来完成你想做的事情,但主要问题是你绑定的数据不支持 INPC. 我让你去读一读,但问题本质上是左手(你的文本显示域)不知道右手(ComboBox)在做什么。当 ComboBox 改变格式时,它必须向任何依赖于它的东西发出信号,说明值已经改变。这就是INPC的作用。如果你愿意的话,你可以自己实现它,网上有很多教程展示如何实现它,但只使用现有的库,如 MVVM灯这就是我在这里要使用的。

在我详细介绍之前,我应该指出,你使用IMultiValueConverter的想法其实是可行的,前提是你把Data和Format作为单独的参数传入,这样每当它们中的任何一个改变值时,它就会更新。其实很多人都会提出这样的解决方案,但这并不是你想实现的最佳方式。转换器和行为实际上只是视图的扩展,当它们暴露了视图的部分时,这是很好的,否则你无法进入。但在你的案例中,问题在于你提供给视图的数据并不是你的视图可以轻易消耗的格式,而在一个 "正确的 "WPF应用程序中,它应该是这样的。事先修复你的数据通常会更快,出错时调试起来也更容易,而且它为单元测试等事情提供了可能性。我们已经知道,为了支持INPC,你的MyDataItem类必须被修改或替换,所以这是一个很好的地方来做你的值到文本逻辑。

因此,首先,为你的模型对象创建一个视图模型,该模型暴露了你想传递给你的视图laye的属性(即 "Format"),并为你想显示的文本字符串添加一个新的属性(即 "DataText")。我们还将创建一个更新函数来填充该字符串,INPC支持和一小部分更新逻辑。

public class MyDataItemViewModel : ViewModelBase
{
    private MyDataItem DataItem;

    public MyDataItemViewModel(MyDataItem dataItem)
    {
        this.DataItem = dataItem;
        UpdateDataText();
    }

    public FormatEnum Format
    {
        get { return this.DataItem.Format; }
        set
        {
            if (this.DataItem.Format != value)
            {
                this.DataItem.Format = value;
                RaisePropertyChanged(() => this.Format);
                UpdateDataText();
            }
        }
    }

    private string _DataText;
    public string DataText
    {
        get { return this._DataText; }
        set
        {
            if (this._DataText != value)
            {
                this._DataText = value;
                RaisePropertyChanged(() => this.DataText);
            }
        }
    }

    private void UpdateDataText()
    {
        switch (this.Format)
        {
            case FormatEnum.Decimal:
                this.DataText = String.Join(", ", this.DataItem.Data.Select(val => String.Format("{0:D}", val)));
                break;

            case FormatEnum.Hex:
                this.DataText = String.Join(", ", this.DataItem.Data.Select(val => String.Format("0x{0:X2}", val)));
                break;

            default:
                this.DataText = String.Empty;
                break;
        }
    }
}

你的 "DataItems "集合需要是公共的,并且可以通过getter来访问(你在原始代码中没有这样做),而且它需要是这些视图模型的集合。

public ObservableCollection<MyDataItemViewModel> DataItems { get; } = new ObservableCollection<MyDataItemViewModel>();

现在对于每一个MyDataItem的实例 你都要把它包裹在MyDataItemViewModel的实例中。

this.DataItems.Add(new MyDataItemViewModel(new MyDataItem { Format = FormatEnum.Decimal, Data = new byte[] { 1, 2, 3 } }));
this.DataItems.Add(new MyDataItemViewModel(new MyDataItem { Format = FormatEnum.Decimal, Data = new byte[] { 4, 5, 6 } }));

现在你的数据的格式就好很多了。ObjectDataProvider提供了一个很好的、方便的方法来把一个特定类型的Enum的所有值放在一个单一的列表中,一个ComboBox可以绑定到这个列表,所以让我们为你的FormatEnum创建一个这样的列表。

xmlns:system="clr-namespace:System;assembly=mscorlib"

<Window.Resources>

    <ObjectDataProvider x:Key="FormatEnumValues" MethodName="GetValues" ObjectType="{x:Type system:Enum}">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="local:FormatEnum"/>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>

</Window.Resources>

现在你的ListView可以直接绑定到这些数据上 而不需要任何转换器或行为或其他类似的乱七八糟的东西。

<ListView ItemsSource="{Binding DataItems}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Format">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox ItemsSource="{Binding Source={StaticResource FormatEnumValues}}" SelectedItem="{Binding Path=Format}" Width="100" />
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>

            <GridViewColumn Header="Data">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding DataText}" TextTrimming="CharacterEllipsis" MinWidth="100" />
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

结果:

enter image description here

© www.soinside.com 2019 - 2024. All rights reserved.