Guy Ben-Moshe
Guy Ben-Moshe

Reputation: 934

WPF bind a dynamic generated slider to function

First: Not a duplicate of Binding Button click to a method --- it's about button, and Relay command can't pass the arguments I need

Also, not a duplicate of How do you bind a Button Command to a member method? - it's a simple method with no arguments - nothing to do with my question.

Obviously (but just to make sure and avoid trolls) not a duplicate of this either Silverlight MVVM: where did my (object sender, RoutedEventArgs e) go?.

Now after clearing this (sorry, I am just really sick of being marked as "duplicate" by people who didn't understand my question), let's talk about the issue: :D

I am trying to bind a generated slider (using data template) to an event (value changed), I know it's impossible to bind an event and I must use ICommand, but I don't know how to get the event arguments to the command function, this is the xaml relevant code: (without the binding since it doesnt work)

<Slider Grid.Column="1"  Grid.Row="1" Height="30" IsSnapToTickEnabled="True"  Maximum="100" SmallChange="1" IsMoveToPointEnabled="True"/>

And this is the function I want it to be binded to:

public void vibrationSlider_move(object Sender, RoutedPropertyChangedEventArgs<double> e)
        {
            VibrationValue = (byte)e.NewValue;
            SendPacket(cockpitType, (byte)Index.VibrationSlider, VibrationValue);
        }

As you can see, I need to use the 'e' coming with the event, I have no idea how to reach it without using the "ValueChanged" slider event.

Notes:

Please don't tell me to add the "ValueChanged" attribute like this:

<Slider ValueChanged="VibrationSlider_move"/>

:)

It's a generated dynamic slider using DataTemplate with an observableCollection, the function isn't in the window.cs file, therefore just using an event is not possible.

Thank you.

Upvotes: 1

Views: 1969

Answers (4)

Janne Matikainen
Janne Matikainen

Reputation: 5121

You could create an extension

public partial class Extensions
{
    public static readonly DependencyProperty ValueChangedCommandProperty = DependencyProperty.RegisterAttached("ValueChangedCommand", typeof(ICommand), typeof(Extensions), new UIPropertyMetadata((s, e) =>
    {
        var element = s as Slider;

        if (element != null)
        {
            element.ValueChanged -= OnSingleValueChanged;

            if (e.NewValue != null)
            {
                element.ValueChanged += OnSingleValueChanged;
            }
        }
    }));

    public static ICommand GetValueChangedCommand(UIElement element)
    {
        return (ICommand)element.GetValue(ValueChangedCommandProperty);
    }

    public static void SetValueChangedCommand(UIElement element, ICommand value)
    {
        element.SetValue(ValueChangedCommandProperty, value);
    }

    private static void OnSingleValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        var element = sender as Slider;
        var command = element.GetValue(ValueChangedCommandProperty) as ICommand;

        if (command != null && command.CanExecute(element))
        {
            command.Execute(element);
            e.Handled = true;
        }
    }
}

which then can be used in xaml as below.

<Slider Minimum="0" Maximum="100" local:Extensions.ValueChangedCommand="{Binding ValueChangedCommand}"/>

Upvotes: 2

Mikko Viitala
Mikko Viitala

Reputation: 8404

As @Philip W stated, you could use e.g. MVVMLight to help dealing with MVVM pattern and with your problem at hand.

You could, for example, have a XAML with DataTemplate and Slider like so:

<Window x:Class="WpfApplication1.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:WpfApplication1"
        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:command="http://www.galasoft.ch/mvvmlight"
        mc:Ignorable="d"
        Title="MainWindow" 
        Height="250" 
        Width="250">
    <Window.Resources>
        <DataTemplate x:Key="SomeTemplate">
            <StackPanel Margin="15">
                <!-- Wrong DataContext can drive you mad!1 -->
                <StackPanel.DataContext>
                    <local:SomeTemplateViewModel />
                </StackPanel.DataContext>
                <TextBlock Text="This is some template"/>
                <Slider 
                    Height="30" 
                    IsSnapToTickEnabled="True" 
                    Maximum="100" 
                    SmallChange="1" 
                    IsMoveToPointEnabled="True">
                    <!-- Bind/pass event as command -->
                    <i:Interaction.Triggers>
                        <i:EventTrigger EventName="ValueChanged">
                            <command:EventToCommand 
                        Command="{Binding Mode=OneWay, Path=ValueChangedCommand}"
                        PassEventArgsToCommand="True" />
                        </i:EventTrigger>
                    </i:Interaction.Triggers>
                </Slider>
                <!-- Show current value, just for sake of it... -->
                <TextBlock 
                    Text="{Binding Value}"
                    FontWeight="Bold"
                    FontSize="24">
                </TextBlock>
            </StackPanel>
        </DataTemplate>
    </Window.Resources>

    <ContentControl ContentTemplate="{StaticResource SomeTemplate}" />
</Window>

So basically you bind desired event to named Command and pass EventArgs to it as parameter. Then in your ViewModel, being the DataContext of you Slider, you handle the event-passed-as-command.

public class SomeTemplateViewModel : ViewModelBase
{
    private double _value;

    public SomeTemplateViewModel()
    {
        // Create command setting Value as Slider's NewValue
        ValueChangedCommand = new RelayCommand<RoutedPropertyChangedEventArgs<double>>(
            args => Value = args.NewValue);
    }

    public ICommand ValueChangedCommand { get; set; }

    public double Value
    {
        get { return _value; }
        set { _value = value; RaisePropertyChanged(); } // Notify UI
    }
}

This would give you something similar to this.

enter image description here

Upvotes: 2

Philip W
Philip W

Reputation: 791

You can use the MVVMLight Toolkit, which allows to send the EventArgs as CommandParameter to the ViewModel:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="ValueChanged">
        <cmd:EventToCommand Command="{Binding ValueChangedCommand}" PassEventArgsToCommand="True"/>
    </i:EventTrigger>
</i:Interaction.Triggers>

In your command.Execute method, you now get an object as parameter which you just have to parse to the correct type...

Upvotes: 4

Alexei - check Codidact
Alexei - check Codidact

Reputation: 23088

Since your slider is dynamically generated, nothing prevents you from adding your ValueChanged event at a later time:

XAML:

<Slider x:Name="slider" HorizontalAlignment="Left" Margin="10,143,0,0" VerticalAlignment="Top" Width="474" Grid.ColumnSpan="2" />

Code-behind:

public MainWindow()
{
    InitializeComponent();

    // it is a good idea to not allow designer to execute custom code
    if (DesignerProperties.GetIsInDesignMode(this))
       return;

    slider.ValueChanged += Slider_ValueChanged;
}

private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
    // do your stuff here
}

Checking design mode is not simple in any context, as pointed out here.

Upvotes: 0

Related Questions