Перевернутая двухсторонняя мультипривязка

Я пытаюсь выразить свойство перечисления в моей модели представления в виде набора переключателей в моем представлении. Все идет нормально; Я могу выразить это с помощью двустороннего MultiBinding:

(rb1.IsChecked, rb2.IsChecked, rb3.IsChecked) ‹-> vm.Value

Используемая здесь множественная привязка будет иметь мультиконвертер, который преобразует между (bool, bool, bool) <-> MyValue; очевидно, одно из (трех) допустимых значений типа MyValue выбирается на основании того, что bool равно true, и наоборот.

Однако это уже немного неудобно: я не могу определить эту привязку в своем представлении Xaml, так как множественные привязки должны быть определены со стороны одного значения. Следовательно, я должен определить множественную привязку в программном коде и использовать SetBinding установите его в свойстве Value моей модели представления.

Теперь проблема, на которой я застрял, заключается в том, что я не просто привязываю один набор переключателей к этому значению, а два. Следовательно, мои привязки должны были бы выглядеть так:

(rbA1.IsChecked, rbA2.IsChecked, rbA3.IsChecked) ‹-> vm.Value ‹-> (rbB1.IsChecked, rbB2.IsChecked, rbB3.IsChecked)

Проблема в том, что я не могу использовать SetBinding для одновременного подключения нескольких привязок к vm.Value.

Решения, которые я пробовал до сих пор:

  • Используйте одну большую мультипривязку, привязку ко всем переключателям одновременно. Это будет означать привязку вида (rbA1.IsChecked, rbA2.IsChecked, rbA3.IsChecked, rbB1.IsChecked, rbB2.IsChecked, rbB3.IsChecked) <-> vm.Value. Проблема с этим решением заключается в том, что если один из переключателей (скажем, rbB2) отмечен галочкой, я не могу сказать, имеет ли rbA2 (не отмеченная отметка) или rbB2 (отмеченная) «новое, правильное» значение.
  • Сначала абстрагируйте группы переключателей, определив элемент управления radio group, который предоставляет только одно свойство SelectedIndex. Затем это свойство можно удобно привязать к моему свойству vm.Value из всех экземпляров моего элемента управления radio group. Хотя это возможно, это требует написания нового класса управления, и мне интересно, единственный ли это способ в WPF.
  • Привяжите один набор переключателей к другому: путем двустороннего связывания rbB1 с rbA1, rbB2 с rbA2 и т. д., а также с использованием множественного связывания только между моим vm.Value и первым набором переключателей, я достиг бы желаемый эффект, но мне не нравится понятие «главная радиогруппа». Это было бы злоупотреблением элементами графического интерфейса для передачи данных.
  • Делайте все в коде программной части и вручную обновляйте переключатели и значение модели просмотра. Конечно, это жизнеспособное резервное решение, но похоже, что оно должно быть реализовано в Xaml/с привязками.

person O. R. Mapper    schedule 22.09.2012    source источник
comment
Использование одного большого мультибиндинга я не совсем понял. У вас есть две группы RadioButton. Вы можете выбрать один элемент из группы1 и один элемент из группы2. Я не понимаю, что «имеет новое правильное значение».   -  person Erti-Chris Eelmaa    schedule 22.09.2012
comment
@ChrisDD: большое множественное связывание будет связывать vm.Value со всеми шестью переключателями одновременно. Шесть переключателей могут иметь начальное состояние, такое как (1, 0, 0, 1, 0, 0). Теперь предположим, что кто-то нажимает 2-й переключатель во 2-м наборе, поэтому мой мультиконвертер вызывается с (1, 0, 0, 0, 1, 0). Как я могу определить, правильный ли первый набор или второй набор, т. е. должно ли быть возвращено первое значение перечисления или второе значение перечисления? Насколько я понимаю, это не удобно.   -  person O. R. Mapper    schedule 22.09.2012
comment
Почему бы вам не использовать одну привязку для каждого RadioButton? Также используйте конвертер и параметр команды. Например: IsChecked={Binding VMEnum, Converter={StaticResource toEnum}, ConverterParameter={YourEnum:FirstButtonChecked}}. Вам нужно реализовать двусторонний преобразователь. В любом случае IsChecked = VMEnum == (CommandParameter as Enum) [в одну сторону]   -  person Erti-Chris Eelmaa    schedule 22.09.2012
comment
то есть для управления RadioBox из ViewMOdel. В противоположном случае логика будет такой: IsChecked==true, затем вернуть CommandParameter. Привязки должны быть двусторонними   -  person Erti-Chris Eelmaa    schedule 22.09.2012
comment
@ChrisDD: Хорошо, если преобразователь вызывается, когда IsChecked изменяется на true. Однако, когда это происходит, свойства IsChecked всех других переключателей меняются на false; что должен вернуть их преобразователь?   -  person O. R. Mapper    schedule 22.09.2012
comment
Вы можете вернуть Binding.DoNothing в конвертере. Или вы также можете создать новое значение перечисления, такое как Enum.NotValid +, вернуть его в конвертере, а в вашем установщике свойств вы измените перечисление только тогда, когда значение! = Enum.NotValid   -  person Erti-Chris Eelmaa    schedule 22.09.2012
comment
@ChrisDD: Интересное решение с Binding.DoNothing. Если вы добавите это как ответ, я приму это.   -  person O. R. Mapper    schedule 22.09.2012


Ответы (2)


Привяжите VMEnum к каждому RadioButton отдельно (используйте одностороннюю привязку). Каждая привязка должна иметь CommandParameter, это перечисление. Нравится:

<RadioButton IsChecked="{Binding VMEnum, Converter={StaticResource EnumConverter}, ConverterParameter={Enums:VMEnums.FirstRadioButtonGroupA}}" />

В преобразователе,

  • Преобразование должно возвращать правильное значение (истина/ложь) в зависимости от VMEnum и COMmandParameter. По сути, логика VMEnum == (YourEnum)CommandParameter.
  • ConvertBack должен возвращать правильный Enum на основе IsChecked. Если IsChecked имеет значение true, вернуть правильное перечисление. В противном случае верните Binding.DoNothing, что прервет привязку для этого конкретного случая.
person Erti-Chris Eelmaa    schedule 22.09.2012
comment
Binding.DoNothing это то, чего мне не хватало! - person O. R. Mapper; 22.09.2012

Использование сложного мультибиндинга с конвертерами и программным обеспечением не только усложнит отладку вашего кода, но и еще больше усложнит его тестирование. На мой взгляд, лучше выразить каждый набор переключателей (флажков) в виде модели представления. Оцените свою ценность, когда какой-либо из переключателей отмечен/не отмечен.

Группа радиокнопок

public class RadioButtonGroup : ViewModel {

    public RadioButtonGroup(string groupName, int count, Action<bool[]> whenAnyChanaged = null) {

        RadioButtons = Enumerable.Range(0, count).Select(_ => {
            var button = new RadioButton { GroupName = groupName };
            button.PropertyChanged += (s, e) => {
                if (e.PropertyName == "IsChecked")
                    whenAnyChanaged(Flags);
            };
            return button;
        }).ToList();           
    }

    public List<RadioButton> RadioButtons { get; private set; }

    public bool[] Flags { get { return RadioButtons.Select(rb => rb.IsChecked).ToArray(); } }
}

Радиокнопка

 public class RadioButton : ViewModel {

    private bool isChecked;

    public bool IsChecked {
        get { return isChecked; }
        set { SetProperty(ref this.isChecked, value); }
    }

    public string GroupName { get; set; }
}

Модель главного представления

public class MainViewModel : ViewModel {
    public MainViewModel() {
        GroupA = new RadioButtonGroup("A", 10, flags => GroupToggle(flags, GroupB.Flags));
        GroupB = new RadioButtonGroup("B", 10, flags => GroupToggle(GroupA.Flags, flags));
    }

    public RadioButtonGroup GroupA { get; private set; }

    public RadioButtonGroup GroupB { get; private set; }

    void GroupToggle(bool[] groupA, bool[] groupB) {
        MyValue = Evaluate(groupA, groupB);
    }
}

Просмотреть

<Window x:Class="WpfLab.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="{Binding Title}" Height="350" Width="525">
<Window.Resources>
    <DataTemplate x:Key="RadioButton">
        <RadioButton IsChecked="{Binding IsChecked, Mode=OneWayToSource}" GroupName="{Binding GroupName}"/>
    </DataTemplate>
</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="30"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>

    <ListBox Grid.Row="0" ItemsSource="{Binding GroupA.RadioButtons}" ItemTemplate="{StaticResource ResourceKey=RadioButton}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>

    <ListBox Grid.Row="1" ItemsSource="{Binding GroupB.RadioButtons}" ItemTemplate="{StaticResource ResourceKey=RadioButton}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
</Grid>

person Per    schedule 22.09.2012
comment
Что делает метод Evaluate, который вы вызываете в своем классе MainViewModel? В частности, когда состояния RadioButtonGroups, например. (1, 0, 0); (0, 1, 0), как он узнает, является ли (1, 0, 0) или (0, 1, 0) предполагаемым новым состоянием? - person O. R. Mapper; 22.09.2012
comment
Прямо сейчас это, вероятно, не будет, но вы можете добавить Enum, например, какая группа была изменена, и добавить ее в GroupToggle и изменить делегаты (MainiewModel()) + передать ее в Evaluate. - person Erti-Chris Eelmaa; 22.09.2012
comment
@ChrisDD: Я думаю, проблема в том, что в то время, когда вызывается Evaluate, уже неизвестно/слишком поздно выяснять, какая группа была изменена. - person O. R. Mapper; 24.09.2012