How to avoid Converters clashing with multibinding in WPF code behind
Asked Answered
K

2

7

I am creating WPF elements dynamically in code behind, and for each of the rows in the Grid I'm building it consists of a CheckBox and a Dynamic number of TextBoxes. The interaction that is needed is the following:

  • If all TextBoxes in a row have a value of 0, set the CheckBox IsChecked property to true and Disable it.
  • If one of the TextBoxes is then changed from 0, enable the CheckBox and set IsChecked to false.
  • If the user clicks on the CheckBox, set all associated TextBoxes to 0 and Disable the CheckBox

I was able to accomplish the first part of the last one using this code:

Binding setScoreToZeroIfIsNormalChecked = new Binding("IsChecked");
setScoreToZeroIfIsNormalChecked.Source = this.NormalCheckBoxControl;
setScoreToZeroIfIsNormalChecked.Converter = m_NormalCheckBoxJointScoresConverter;
tempJointScoreControl.JointScoreContainer.SetBinding(ContainerBase.SingleAnswerProperty, setScoreToZeroIfIsNormalChecked);

and the converter:

public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
    if (value is bool && targetType == typeof(Answer))
    {
        if ((bool)value)
        {
            Answer answer = new Answer();
            answer.Value = "0";
            answer.DisplayValue = "0";
            return answer;
        }
        else
            return null;
    }
    else
    {
        return null;
    }
}

However, in attempting to create another converter to accomplish other functionality, I was running into issues of converters stepping on one another since all functionality is based around the CheckBox.IsChecked property.

Is there anyway to accomplish all of the above using one or two multibinding converters? I'd really like to avoid having to create a whole bunch of events and maintaining them in order to do this.

Kornher answered 21/8, 2012 at 14:29 Comment(3)
Is there a specific reason why you are creating the controls in code?Starry
Are you using the MVVM design pattern? This would be pretty simple logic to accomplish in your data item instead of using Converters to make your UI accomplish what you want. If you're not using MVVM, it would probably be best to just handle CheckBox.Click and TextBox.TextChanged events and adjust the values of the other objects in the code behind. You should only need two events, one for all CheckBoxes and one for all TextBoxes. Both can only affect items that share the same Grid.Row as the changed item.Hayfork
@DanielHilgarth Everything is created on the fly dynamically, so all of my xaml for the UserControls is only representative of a single instance.Kornher
D
13

It's relatively easy. Everything should resolve around CheckBox IsChecked property. For a simple reason, it's a two-way property. So either you can modify it, or CheckBox can modify it.

So what you do, you use MultiBinding, as such:

    MultiBinding multiBinding = new MultiBinding();
    multiBinding.Converter = multiBindingConverter;

    multiBinding.Bindings.Add(new Binding("Text") { Source = txtbox1});
    multiBinding.Bindings.Add(new Binding("Text") { Source = txtbox2});

    multiBinding.NotifyOnSourceUpdated = true;//this is important. 
    checkBox.SetBinding(CheckBox.IsCheckedProperty, multiBinding);

And in your multiBindingConverter, you will have object[] value as first parameter, which you need to convert into IList and iterate over it && do your calculations, if you should either return true/false.(IsChecked=true or false)

Now bind CheckBox IsEnabled to CheckBox IsChecked property, and use BooleanInverterConverter. (If CheckBox is checked, it should be disabled, and vice versa)

The last step is to make TextBoxes listen to actual IsChecked property of CheckBox. If it is TRUE, they all should show value of 0, otherwise they can show what they want.

So, make a new MultiBinding.

    MultiBinding multiBinding = new MultiBinding();
    multiBinding.Converter = textboxMultiBindingConverter;

    multiBinding.Bindings.Add(new Binding("IsChecked") { Source = checkbox1});
    multiBinding.Bindings.Add(new Binding("Text") { Source = textbox1});

    multiBinding.NotifyOnSourceUpdated = true;//this is important. 
    textbox1.SetBinding(TextBox.Text, multiBinding);

the idea in textboxMultiBindingConverter is to either return Text(value[1]) if value[0]==FALSE or "0" if value[0]==TRUE.

Dyspnea answered 21/8, 2012 at 15:26 Comment(2)
Thanks, this was extremely helpful! I was able to implement a lot of this, However I'm getting errors when the ConvertBack methods are being called (e.g. when I click on the CheckBox). I've never seen the ConvertBack() method in a MultiBinding converter implemented, do I need to set the Binding Mode to OneWay?Kornher
thanks for this; with some modifications I was able to accomplish everything with bindings/converters except for setting all the associated text boxes in the row to '0' when the checkbox is toggled; It was proving to be very messy/convoluted with how the controls were being dynamically created so all I had to do was add one listener to CheckBox.IsChecked with a simple foreach loop.Kornher
S
1

This problem can be solved very easily if you would use MVVM.

You would have a ViewModel that represents a row in the grid. It would have a property per textbox and one for the checkbox.

Additionally you would have a ViewModel for the View containing the Grid and this ViewModel would expose a collection of row ViewModels.

The ViewModel for your row:

public class AnswersViewModel : ViewModelBase // From MvvmLight
{
    public bool IsAnswered
    {
        get { return _isAnswered; }
        set
        {
            if(value == _isAnswered)
                return;
            _isAnswered = value;
            if(_isAnswered)
            {
                Answer1 = "0";
                Answer2 = "0";
            }

            RaisePropertyChanged("IsAnswered");
        }
    }

    public string Answer1
    {
        get { return _answer1; }
        set
        {
            if(value == _answer1)
                return;

            _answer1 = value;
            RaisePropertyChanged("Answer1");

            if(_answer1 == "0" && _answer2 == "0")
            {
                _isAnswered = true;
                RaisePropertyChanged("IsAnswered");
            }
        }
    }

    // The implementation of Answer2 is similar to Answer1
}

The ViewModel for the View:

public class FooViewModel : ViewModelBase
{
    public ObservableCollection<AnswersViewModel> Answers
    {
        get { return _answers; }
    }
}

Your View would contain the Grid with ItemsSource="{Binding Answers}" and a ControlTemplate for the items which binds to the properties of AnswersViewModel.

Disabling the CheckBox I would handle via a Trigger in a Style.

Starry answered 21/8, 2012 at 14:40 Comment(3)
Do you have any examples of MVVM? I've used it before in a couple of simpler applications, but I'd be lying if said I fully understood what was going on; I've never been able to completely wrap my ahead around it and ended up fumbling through it with lots of trial & errorKornher
@user564636 I have a very basic MVVM example on my blog if you're interested. I also had a hard time understanding the MVVM design pattern at first since all the existing material was too high-level for me at the time, so once I understood it I wrote a very basic MVVM example for my blog to try and help others have an easier time understanding it too. It really is a great design pattern if you're working with WPF :)Hayfork
@Hayfork and Daniel, Thank you both for providing me some insight on the MVVM approach! This is definitely something that I will learn and look to implement in future applications. Unfortunately, due to time constraints, it's not feasible to convert what has already been developed into the MVVM paradigm.Kornher

© 2022 - 2024 — McMap. All rights reserved.