【问题标题】:UserControl Binding Not Returning Correct ValueUserControl 绑定未返回正确的值
【发布时间】:2019-11-30 14:42:56
【问题描述】:

编辑: Repro Download (.zip)

我制作了一个由 3 个滑块和一些标签组成的 UserControl。用于操作类的平移、旋转和缩放值。

每个用户控件都有自己的平移、旋转和缩放属性。对应滑块的 Value 绑定到该属性。

这一切都可以正常工作,直到用户尝试通过用鼠标滑动滑块来手动更改值。无论出于何种原因,这都不会更新属性。

这是如何设置其中一个滑块的示例:

<Slider x:Name="sliderTranslation" Grid.Column="1" Grid.Row="1" VerticalAlignment="Center" ToolTip="{Binding Value, RelativeSource={RelativeSource Self}}" Value="{Binding Path=Translation}" Thumb.DragCompleted="SliderTranslation_DragCompleted" Maximum="65535" TickFrequency="0" SmallChange="1" AutoToolTipPlacement="TopLeft"/>

这就是我的 DataGrid 的设置方式:

<DataGrid x:Name="dgValueList" Margin="10,72,10,76" SelectionMode="Single" IsReadOnly="True" BorderThickness="2" AlternationCount="2" EnableRowVirtualization="False">
    <DataGrid.Columns>
        <DataGridTemplateColumn Header="Face Values" Width="*" CanUserReorder="False">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <local:FaceValueSlider/>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>
</DataGrid>

所以在某些情况下。 DataGrid 由这些用户控件中的 49 个组成。所以基本上总共有 147 个滑块。

让我们以第一个 UserControl 为例,它具有这些值;
翻译:3380
轮换:49972
规模:16807

如果我将翻译滑块移动到最大值 65535 并保存,我得到的返回值仍然是 3380。但是,如果我通过添加的方法更新它们,它会按预期工作。只有当他们尝试手动滑动时才会这样做。

除此之外,我还收到 51 条与 UserControls 相关的警告,我不知道它们的含义。这是其中的 2 个:

System.Windows.Data 警告:4:无法找到与引用“RelativeSource FindAncestor,AncestorType='System.Windows.Controls.ItemsControl',AncestorLevel='1'' 的绑定源。 BindingExpression:Path=Horizo​​ntalContentAlignment;数据项=空;目标元素是'ListBoxItem'(名称='');目标属性是“Horizo​​ntalContentAlignment”(类型“Horizo​​ntalAlignment”)

System.Windows.Data 警告:4:无法找到与引用“RelativeSource FindAncestor,AncestorType='System.Windows.Controls.ItemsControl',AncestorLevel='1'' 的绑定源。绑定表达式:路径=(0);数据项=空;目标元素是'ListBoxItem'(名称='');目标属性是'ClearTypeHint'(类型'ClearTypeHint'),

我做错了这整个绑定的事情吗?我尝试将 UserControls 添加到列表中,而不是在创建它们并设置 DataGrid 的 ItemsSource 时。

但最终看起来像这样。

【问题讨论】:

  • 请提供minimal reproducible example。您正在向我们询问有关 viewmodel 和 XAML 之间的用户控件中的交互,但我们从未见过。 “在创建用户控件时将它们添加到列表中并设置 DataGrid 的 ItemsSource”——我可能会花费接下来的一百万年时间来尝试猜测这对您的实际代码意味着什么,永远不要靠近。
  • 抱歉,我不应该急于提出这个问题。我制作了一个重现项目并在 OP 顶部发布了一个链接。希望这足以帮助找出我做错了什么。 :)

标签: c# wpf binding datagrid datatemplate


【解决方案1】:

这里有一个MVVM example 可以帮助您入门。阅读并理解该文章以了解此处的基本操作原理。使用它来获取以下代码的ObservableObject 基类。

这里有很多错误,向您展示更正的代码比解释所有内容更容易。阅读我上面链接的文章并研究此代码。我还没有完全按照我的方式重新设计它:例如,没有主视图模型。这现在不是 MVVM 的一个很好的例子,但它说明了你在网格中放置了什么样的东西,如何编写模板列,以及如何正确更新属性。

首先,您将用户控件的实例用作同一控件的视图模型,但它不是真正的视图模型,因为它从不引发任何属性更改通知。让我们为网格编写一个实际的项目视图模型。这不是 UI 控件。它是数据,将在 UI 控件中显示。它有信息,当它的信息发生变化时它会得到通知。如果你愿意,它也可以有一些逻辑。

public class SliderItem : ObservableObject
{
    public SliderItem()
    {
    }

    public SliderItem(int trans, int rot, int scale)
    {
        Translation = trans;
        Rotation = rot;
        Scale = scale;
    }

    public void UpdateValues(int newTrans, int newRot, int newScale)
    {
        Translation = newTrans;
        Rotation = newRot;
        Scale = newScale;
    }

    public void UpdateDescription(string newText)
    {
        if(!String.IsNullOrWhiteSpace(newText))
        {
            Description = newText;
            OnPropertyChanged(nameof(Description));
        }
    }

    public String Description { get; private set; }

    private int _translation = 0;
    public int Translation
    {
        get { return _translation; }
        set
        {
            if (value != _translation)
            {
                _translation = value;
                OnPropertyChanged(nameof(Translation));
            }
        }
    }

    private int _rotation = 0;
    public int Rotation
    {
        get { return _rotation; }
        set
        {
            if (value != _rotation)
            {
                _rotation = value;
                OnPropertyChanged(nameof(Rotation));
            }
        }
    }

    private int _scale = 0;
    public int Scale
    {
        get { return _scale; }
        set
        {
            if (value != _scale)
            {
                _scale = value;
                OnPropertyChanged(nameof(Scale));
            }
        }
    }
}

TripleSlider.xaml

您的 TripleSlider 的 XAML 基本没问题;主要问题是它正在寻找以前不存在的视图模型。但是我们还希望滑块值绑定在Value 更改时更新绑定属性,而不是在滑块控件失去焦点时(这是不明显的默认行为)。所以将UpdateSourceTrigger=PropertyChanged 添加到所有三个 Slider.Value 绑定

    Value="{Binding Path=Translation, UpdateSourceTrigger=PropertyChanged}" 

TripleSlider.xaml.cs

就是这样。这就是该类的外观。

public partial class TripleSlider : UserControl
{
    public TripleSlider()
    {
        InitializeComponent();
    }
}

MainWindow.xaml 很好。 MainWindow.xaml.cs 有点变化:

public partial class MainWindow : Window
{
    //  Don't use arrays. Use ObservableCollection<WhateverClass> for binding to UI controls,
    //  use List<Whatever> for anything else. 
    private ObservableCollection<SliderItem> _sliders = new ObservableCollection<SliderItem>();
    public MainWindow()
    {
        InitializeComponent();

        //  The ObservableCollection will notify the grid when you add or remove items
        //  from the collection. Set this and forget it. Everywhere else, interact with 
        //  _sliders, and let the DataGrid handle its end by itself. 
        //  Also get rid of EnableRowVirtualization="False" from the DataGrid. Let it 
        //  virtualize. 
        myDataGrid.ItemsSource = _sliders;
    }

    private void BAddControls_Click(object sender, RoutedEventArgs e)
    {
        for (int i = 0; i < 49; i++)
        {
            var newSlider = new SliderItem();
            newSlider.UpdateDescription(String.Format("{0}: Unkown Value", (i+1)));
            newSlider.UpdateValues((i+1)*1337, (i+1)*1337, (i+1)*1337);
            _sliders.Add(newSlider);
        }

        bAddControls.IsEnabled = false;
    }

    private void BFetchValues_Click(object sender, RoutedEventArgs e)
    {
        if (myDataGrid.SelectedItem != null)
        {
            var selectedSlider = myDataGrid.SelectedItem as SliderItem;
            MessageBox.Show(String.Format("Translation: {0}\nRotation: {1}\nScale: {2}", selectedSlider.Translation, selectedSlider.Rotation, selectedSlider.Scale), "Information", MessageBoxButton.OK, MessageBoxImage.Information);
        }
    }

    private void MyDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        bFetchValues.IsEnabled = (myDataGrid.SelectedItem != null) ? true : false;
    }
}

【讨论】:

  • 非常感谢您的快速回复和非常详细的回答!我已经着手对我的复制项目进行了这些更改。但是,它仍然存在同样的问题 - 当您移动滑块时,这些值实际上并没有更新。我需要对 ValueChanged 事件做些什么吗?这些不会和绑定冲突吗?我对 WPF 相当陌生,所以所有这些绑定仍然有点令人困惑......
  • @Dealman 我遗漏了一件事。请参阅TripleSlider.xaml 的更新。
  • “这些不会与绑定冲突吗?” 我认为您在那里做出了某种假设,但我不确定是什么。我猜你想到的事件是PropertyChanged。我给你的基类中的OnPropertyChanged 引发PropertyChanged 事件,前提是有人在某处添加了一个处理程序(也就是说,如果它不为空,则引发它:如果它为空,则尝试引发它会抛出一个空引用异常)。我不知道您可以对“与绑定冲突”的事件做些什么。绑定将处理程序添加到它。
  • ...他们还向 Slider 添加了一个处理程序,以侦听对其 Value 属性的更改。然后他们坐在那里。当 viewmodel 调用 propertychanged 处理程序时,绑定会更新“目标”属性 Slider.Value。当 Slider 发出其值已更改的通知时,绑定再次唤醒并更新您的 viewmodel 属性。
  • 我的意思是,如果我尝试对滑块使用 ValueChanged 事件,它们会被覆盖/忽略,因为滑块的值绑定到 Translation。但这只是我不知道自己在做什么的问题:P 这确实有效,现在我将更新我的主要项目,它应该很快就可以在 GitHub 上发布了!非常感谢您的帮助和出色的参考资料!
猜你喜欢
  • 2020-06-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-23
  • 2015-09-02
  • 1970-01-01
  • 2019-11-01
  • 2013-09-11
相关资源
最近更新 更多