Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binding A Single enum with Flags to Multiple Checkboxes in WPF?

I have the following Enum:

[Flags]
public enum SubscriberLevel {
    BRONZE = 1,
    SILVER1 = 2,
    SILVER2 = 4,
    SILVER = SubscriberLevel.SILVER1 | SubscriberLevel.SILVER2,
    GOLD1 = 8,
    GOLD2 = 16,
    GOLD3 = 32,
    GOLD = SubscriberLevel.GOLD1 | SubscriberLevel.GOLD2 | SubscriberLevel.GOLD3,
    DIAMOND1 = 64,
    DIAMOND2 = 128,
    DIAMOND3 = 256,
    DIAMOND = SubscriberLevel.DIAMOND1 | SubscriberLevel.DIAMOND2 | SubscriberLevel.DIAMOND3,
    ALL = SubscriberLevel.BRONZE | SubscriberLevel.SILVER | SubscriberLevel.GOLD | SubscriberLevel.DIAMOND
}

I am working on a control that should allow a user to dictate one, many, or all flags in this enum through a series of checkboxes, one for each Enum in the list.

I have an Enum member in the control, and a property to expose the Enum. The control itself inherits the INotifyPropertyChanged method, and the Setter of the property correctly calls the OnPropertyChanged event handler when the value gets set.

I even see some semblance of functionality within the control, but what I don't see is it behaving properly:

enter image description here

Say user so and so unchecks the ALL option; I want every single check box to reflect this by being cleared as well, but I am not seeing that happen.

As of right now, I think my problem is that I am binding the Checked state of these checkboxes to the Enum itself, and what I need to be doing is binding the Enum to these checked boxes, and have each check box represent a specific flag in the Enum set.

I've noticed in debugging the control that, when I uncheck the check box, the value is not getting set. Why is that?

I know I'll need to use a converter to accomplish this, but for right now; how do I go about accomplishing this on the XAML side; defining the Datacontext of the control as the control itself, and then tieing the Enum flags to their respective check boxes?

This is an example of one of the check boxes :

<CheckBox
    x:Name="chkAll" Grid.Row="1" VerticalContentAlignment="Center" VerticalAlignment="Center"
    IsChecked="{Binding Path=SubLevel, Converter={StaticResource ETBC},
    ConverterParameter={x:Static Enums:SubscriberLevel.ALL}, Mode=TwoWay}"/>

This is the Converter:

public class EnumToBoolConverter : IValueConverter {
    public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
        return ( ( Enum )value ).HasFlag( ( Enum )parameter );
    }

    public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
        return value.Equals( true ) ? parameter : Binding.DoNothing;
    }
}

This is the code for the control (as it stands):

public partial class UCSubscriptionLevels : UserControl, INotifyPropertyChanged {
    private SubscriberLevel _SubLevel = SubscriberLevel.ALL;
    public event PropertyChangedEventHandler PropertyChanged;
    public UCSubscriptionLevels( ) { InitializeComponent( ); }

    /// <summary>
    /// Get or Set Subscriber Levels to Build.
    /// </summary>
    public SubscriberLevel SubLevel {
        get { return this._SubLevel; }
        set {
            this._SubLevel = value;
            if ( this.PropertyChanged != null )
                this.PropertyChanged( this, new PropertyChangedEventArgs( "SubLevel" ) );
        }
    }       
}

EDIT

It's been a while; I forgot about Dependency Properties so I chose to implement a Dependency Property on the control rather than a property member. The behavior is still sketchy. This is the new code for the property in the control:

public static readonly DependencyProperty SubscriberLevelProperty =
            DependencyProperty.Register( "SubLevel", typeof( SubscriberLevel ),
            typeof( UCSubscriptionLevels ), new PropertyMetadata( SubscriberLevel.ALL ) );
.
.
.
/// <summary>
/// Get or Set Subscriber Levels to Build.
/// </summary>
public SubscriberLevel SubLevel {
    get { return ( SubscriberLevel )this.GetValue( UCSubscriptionLevels.SubscriberLevelProperty ); }
    set { this.SetValue( UCSubscriptionLevels.SubscriberLevelProperty, value ); }
}

However, it's still not behaving properly...

Also; I had to change the Enum (Bronze = 0 to 1, etc).

like image 981
Will Avatar asked Nov 09 '22 10:11

Will


1 Answers

Okay; It's sort of a really annoying workaround, and probably as far away from the CORRECT solution as you can get, but I was able to conceive of a method for handling for what I am trying to do. It is all done in the code behind; there is no binding happening at all in the XAML.

First is the Dependency Property:

public static readonly DependencyProperty SubscriberLevelProperty =
            DependencyProperty.Register( "SubLevel", typeof( SubscriberLevel ),
            typeof( UCSubscriptionLevels ), new FrameworkPropertyMetadata( SubscriberLevel.ALL, FrameworkPropertyMetadataOptions.None, ( S, E ) => {
                SubscriberLevel NV = ( SubscriberLevel )E.NewValue;
                UCSubscriptionLevels sender = S as UCSubscriptionLevels;
                sender.GetAllChildren<CheckBox>( ).ToList( ).ForEach( chk => {
                    chk.Checked -= sender.CheckedChanged;
                    chk.Unchecked -= sender.CheckedChanged;
                    chk.IsChecked = NV.HasFlag( sender.dctChkSL[chk] );
                    chk.Checked += new RoutedEventHandler( sender.CheckedChanged );
                    chk.Unchecked += new RoutedEventHandler( sender.CheckedChanged );
                } );
            } ) );

The major change here is that the property handles incoming data changes itself. I've done this before with other things and had a measure of success, so I tried it here.

In the Loaded event, the control adds an event handler to each Checkbox Checked and Unchecked event. This callback removes that event handler (to avoid an infinite loop), adjusts the checked state such that if it finds within the controls Dictionary, it marks it as checked; otherwise, unchecks it, and then adds the event handler to the events again.

All the event handler does is, well

private void CheckedChanged( object sender, RoutedEventArgs e ) { this.SubLevel ^= this.dctChkSL[sender as CheckBox]; }.

This is the dictionary :

private Dictionary<CheckBox, SubscriberLevel> dctChkSL;

This is the initializer for the dictionary and the Checkboxes Checked/Unchecked event handlers.

this.dctChkSL = new Dictionary<CheckBox, SubscriberLevel>( ) {
    {this.chkAll, SubscriberLevel.ALL}, {this.chkBronze, SubscriberLevel.BRONZE},
    {this.chkSilver, SubscriberLevel.SILVER}, {this.chkSilver1, SubscriberLevel.SILVER1},
    {this.chkSilver2, SubscriberLevel.SILVER2},
    {this.chkGold, SubscriberLevel.GOLD}, {this.chkGold1, SubscriberLevel.GOLD1},
    {this.chkGold2, SubscriberLevel.GOLD2}, {this.chkGold3, SubscriberLevel.GOLD3},
    {this.chkDiamond, SubscriberLevel.DIAMOND}, {this.chkDiamond1, SubscriberLevel.DIAMOND1},
    {this.chkDiamond2, SubscriberLevel.DIAMOND2}, {this.chkDiamond3, SubscriberLevel.DIAMOND3}
};
this.Loaded += ( S, E ) =>
    this.GetAllChildren<CheckBox>( ).ToList( ).ForEach( chk => {
        chk.Checked += new RoutedEventHandler( this.CheckedChanged );
        chk.Unchecked += new RoutedEventHandler( this.CheckedChanged );
    } );

As much as I was hoping for a more eloquent solution, as I wasn't really able to find one, this will have to do until someone can provide a better answer.

like image 77
Will Avatar answered Nov 14 '22 23:11

Will