UserControl 绑定未返回正确的值

UserControl Binding Not Returning Correct Value

编辑: Repro Download (.zip)

我制作了一个由 3 个滑块和一些标签组成的 UserControl。用于操纵 class.

的平移、旋转和缩放值

每个 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 个这样的 UserControl 组成。所以基本上总共有 147 个滑块。

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

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

除此之外,我还收到 51 条与用户控件相关的警告,我不知道它们是什么意思。这是其中的 2 个:

System.Windows.Data Warning: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=HorizontalContentAlignment; DataItem=null; target element is 'ListBoxItem' (Name=''); target property is 'HorizontalContentAlignment' (type 'HorizontalAlignment')

System.Windows.Data Warning: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.ItemsControl', AncestorLevel='1''. BindingExpression:Path=(0); DataItem=null; target element is 'ListBoxItem' (Name=''); target property is 'ClearTypeHint' (type 'ClearTypeHint'),

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

但最终看起来像这样

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

这里有很多错误,向您展示更正后的代码比解释所有内容更容易。阅读我上面链接的文章并研究这段代码。我还没有按照我的方式完全重新设计:例如,没有主视图模型。这现在不是 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 更改时更新绑定属性,而不是在 Slider 控件失去焦点时(这是 non-obvious 默认行为)。因此,将 UpdateSourceTrigger=PropertyChanged 添加到 所有三个 Slider.Value 绑定

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

TripleSlider.xaml.cs

就是这样。这就是 class 应该的样子。

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;
    }
}