简体   繁体   中英

WPF button binding to a property Convertback IMultiValueConverter does not update value

I have a binding from a toggle button hooked up in code behind. I want to bind the isChecked state from one button to 4 video controls to toggle the mute function. I am using multibinding to bind the toggle button to 4 different controls. My problem is using breakpoints i can see everything is triggered right up to the property mute property on each object, but the property "value" parameter never gets updated. It stays at the default setting when the controls are instantiated.

So first i create the bindings in code behind

        IMultiValueConverter converter = new EmptyMultiValueConverter();
        MultiBinding myMultiBinding = new MultiBinding();
        myMultiBinding.Converter = converter;
        myMultiBinding.UpdateSourceTrigger = UpdateSourceTrigger.Default;
        myMultiBinding.Mode = BindingMode.OneWayToSource;      
        myMultiBinding.NotifyOnSourceUpdated = true;  



        for (int i = 1; i < _maxNumberofPlayers; i++)
        {
            VideoPlayer player = new VideoPlayer()
            {
                Mute = false
            };

          myMultiBinding.Bindings.Add(new Binding("Mute") { Source = player 
         });

        }


     btnMuteToggle.SetBinding(SimpleButton.IsCheckedProperty, myMultiBinding);

This all seems to work because when i click on the button i can see in the multivalue converter the correct isChecked button status arrives at the breakpoints, at ConvertBack below i can confirm the value is the correct bool that reflects the toggle button state.

public class EmptyMultiValueConverter : IMultiValueConverter
{
    #region IMultiValueConverter Members

    public object Convert(object[] values, Type targetType, object 
    parameter, System.Globalization.CultureInfo culture)
    {
        // gets from the object source
        return (bool)values[0];
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object 
   parameter, System.Globalization.CultureInfo culture)
    {

        return new Object[] {value,value,value,value};
    }

    #endregion
}

At this point i can confirm it hits the Mute property, and triggers the SET 4 times, but as i trace through it the value parameter remains at the previously set value, and does not update to reflect the value passed to it via ConvertBack

   // mute property in the media player user control
    public bool Mute
    {
        get { return  _media.IsMuted; }
        set
        {       
            if (_media.IsMuted == value)
                return;
            else
            {
                _media.IsMuted = value;
                NotifyPropertyChanged("Mute");
            }

        }
    }

Can anyone help please. Have been tearing my hair out for 3 days.

It seemed to me using multibinding is an efficient way to hook up 4 seperat controls players and bind them to one button click.

well I have tried several options again and none of them work except code in a click button event

Multibinding just does not work at all. I can see in break points that the Mute property is being called 4 times for 4 controls but the value parameter is never updated to the new property value.

I also tried binding the one button to 4 different controls

foreach(Player player in lsPlayers)
{
    btnMuteToggle.SetBinding(SimpleButton.IsCheckedProperty, new Binding("Mute")
       {
         Source = player,
         UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
         Mode = BindingMode.OneWayToSource
        });
}

This just results in one player object mute property being called. You cant bind more than one control to the same button. It just does not work.

so I just called the 4 objects in a click event.

I believe that in this case you may want to bind those 4 properties to the mute property individually and make sure each is set Mode to TwoWay. But if MultiValueConverter supports it, I can't particularly argue with your approach.

It appears that you have not made a Dependency Property for Mute. this is vital to the WPF Binding Workflow and tends to scale easier than NotifyPropertyChanged as NotifyPropertyChanged can tend towards requiring you to manage more. A DependencyProperty will abstract away both sending and receiving the updates to your property and the Xaml editor support for it is better.

https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/how-to-implement-a-dependency-property

I find WPF Binding to be nice in theory, but still requires a lot more leg work than it needs to. Have a function. I add it to all of my WPF viewmodels and controls.

/// <summary>
/// simplify wpf binding
/// </summary>
/// <param name="name"></param>
/// <param name="type"></param>
/// <param name="preferNull">Fair Warning. if you create an instance, you will have also created a singleton of this property for all instances of this class. </param>
/// <returns></returns>
private static DependencyProperty AddDp(string name, Type type, bool preferNull = true)
{
    return DependencyProperty.Register(name, type, typeof(Setting),
        new PropertyMetadata((!preferNull || type.IsValueType) ? Activator.CreateInstance(type) : null));
}

Just inherit from DependencyObject and you can make mute look more like this:

public static readonly DependencyProperty MuteDp = AddDp(nameof(Mute), typeof(bool));
public bool Mute
{
    get => (bool)GetValue(MuteDp);
    set { if(value != Mute) SetValue(MuteDp, value); }
}

But Be Warned! Sometimes WPF Binding to a Dependency Property updates the Dependency Property's internal values without entering the setter of its corresponding property! This was the case on a MultiValueBinding I used. meaning it may never touch the Mute::Set accessor. This can be pretty problematic, to counter this issue you can use the various callbacks available to a DependencyObject!

private static DependencyProperty AddDp(string name, Type type, bool preferNull = true)
{
    var dp = DependencyProperty.Register(name, type, typeof(Setting),
        new PropertyMetadata(
            (!preferNull || type.IsValueType) ? Activator.CreateInstance(type) : null
            ,new PropertyChangedCallback((dobj, dpe)=>
            {
                //after property changed.
                //forcing usage of set accessor
                ((MuteContainerObj)dobj).Value = ((strong text)dobj).Value;
                //or forcibly use an update function
                ((MuteContainerObj)dobj).MuteUpdated();
            }),new CoerceValueCallback((dobj, o)=> {
                //before property changed
                return o;
            })), new ValidateValueCallback((o)=> {
                //before property change events
                return true;
            }));
    return dp;
        
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM