0
votes

Edit: Repro Download (.zip)

I've made a UserControl consisting of 3 sliders and some labels. Meant for manipulating translation, rotation and scale values of a class.

Each UserControl has their own Translation, Rotation and Scale property. The Value of the corresponding slider is bound to this property.

This all works as it should until the user tries to manually change the value by sliding the slider with their mouse. For whatever reason, this doesn't update the property.

This is an example of how one of the sliders are set up:

<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"/>

And this is how my DataGrid is set up:

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

So for some context. The DataGrid consists of 49 of these UserControls. So essentially there are 147 sliders in total.

Lets use the very first UserControl as an example, it has these values;
Translation: 3380
Rotation: 49972
Scale: 16807

If I move the Translation slider to its maximum which is 65535 and save, the returning value I get is still 3380. However if I update them via a method I added it works as intended. It's only when they try and slide it manually it does this.

On top of that I also get 51 warnings related to the UserControls which I have no idea what they mean. Here's 2 of them:

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'),

Am I doing this whole binding thing wrong? I've tried adding the UserControls to a List instead as they're created and setting the ItemsSource of the DataGrid.

But that ends up looking like this.

1
Please provide a minimal reproducible example. You're asking us about interactions in your usercontrol between the viewmodel and the XAML, but we've never seen either. "adding the UserControls to a List instead as they're created and setting the ItemsSource of the DataGrid" -- I could spend the next million years trying to guess what that means in terms of your actual code, and never come close.15ee8f99-57ff-4f92-890c-b56153
Sorry, I shouldn't have rushed making the question. I've made a repro project and posted a link at the top of the OP. Hopefully it's enough to help figure out what I'm doing wrong. :)Dealman

1 Answers

1
votes

Here's an MVVM example to get you started. Read and understand that article to get a sense of the basic principles in operation here. Use that to get the ObservableObject base class for the below code.

There's so much wrong here, it's easier to show you the corrected code than to explain everything. Read the article I linked above and study this code. I haven't fully reworked this the way I'd do it: There's no main viewmodel, for example. This is not now a good example of MVVM, but it illustrates what kind of things you put in a grid, how to write a template column, and how you can have properties update properly.

First, you're using instances of your usercontrol as a viewmodel for the same control, except it's not really a viewmodel because it never raises any property change notifications. Let's write an actual item viewmodel for the grid. This is not a UI control. It is data, which will be displayed in UI controls. It's got information, and it's got notifications when its information changes. It could have some logic as well if you wanted.

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

Your XAML for TripleSlider is mostly fine; the main problem is that it's looking for a viewmodel that wasn't there before. But we also want the slider value bindings to update the bound properties when Value changes, not when the Slider control loses focus (which is the non-obvious default behavior). So add UpdateSourceTrigger=PropertyChanged to all three Slider.Value bindings.

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

TripleSlider.xaml.cs

This is it. This is what that class should look like.

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

MainWindow.xaml is fine. MainWindow.xaml.cs changes a bit:

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