My data is loaded from the database, and a list of RadioButtons is dynamically created. Here is what my ItemsControl looks like:

<ItemsControl ItemsSource="{Binding Woods}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <StackPanel/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <RadioButton Content="{Binding Name}" GroupName="WoodGroup" FontSize="10" Command="{Binding DataContext.RequestWoodChange, RelativeSource={RelativeSource FindAncestor,AncestorType=Page}}" CommandParameter="{Binding}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> 

The question is quite easy in theory, but I can not solve it. How to set the first RadioButton to be selected (IsChecked = true)? The bottom line is that the VM does not have the IsChecked property; it is not necessary in principle.

  • one
    If IsChecked is not needed, why are you using it? - tym32167
  • Then tell us about the meaning of your VM. What's the point of your RequestWoodChange team? - VladD
  • @ tym32167 the fact is that when I launch the application, then not one of the RadioButton group is selected. And I need to make sure that at least one is always selected. - Bretbas Nov.
  • @VladD when the RequestWoodChange command is RequestWoodChange , I immediately get the Wood object in the handler in the parameter, which is very convenient than getting the number, then look for it in the collection, etc. - Bretbas
  • @Bretbas: This is understandable, but what is the point of your team? What she does? - VladD

2 answers 2

The standard way in such cases is to create a wrapper, I use something like this, a universal one:

 class Wrapper<T> : Vm { public T Value { get; } public Wrapper(T value) => Value = value; bool isChecked; public bool IsChecked { get => isChecked; set { if (Set(ref isChecked, value)) CheckedChanged?.Invoke(Value, IsChecked); } } public event Action<T, bool> CheckedChanged; } 

For convenience, the event to which we subscribe is defined here.

So, let me have a VM class:

 class TravelMethod : Vm { public string Name { get; } public TravelMethod(string name) => Name = name; } 

and in the main VM a collection of objects of this class and a property for the selected element:

 class MainVM : Vm { public ObservableCollection<TravelMethod> TravelMethods { get; } = new ObservableCollection<TravelMethod> { new TravelMethod("Walk"), new TravelMethod("Bicycle"), new TravelMethod("Car"), new TravelMethod("Train"), new TravelMethod("Plane") }; TravelMethod selectedTravelMethod; public TravelMethod SelectedTravelMethod { get => selectedTravelMethod; set => Set(ref selectedTravelMethod, value); } 

Based on this collection, we need to generate a collection of wrappers, I do it in the constructor, you determine the right moment yourself - most likely it will be after loading data from the database:

  public ObservableCollection<Wrapper<TravelMethod>> WrappedTravelMethods { get; } public MainVM() { WrappedTravelMethods = new ObservableCollection<Wrapper<TravelMethod>>( TravelMethods.Select(t => new Wrapper<TravelMethod>(t))); foreach (var wtm in WrappedTravelMethods) wtm.CheckedChanged += WtmCheckedChanged; } private void WtmCheckedChanged(TravelMethod travelMethod, bool isChecked) { if (isChecked) SelectedTravelMethod = travelMethod; } } 

When setting the IsChecked flag, we set this selected item as the current one.

Now, in order to immediately set the first element as the selected one, you need to add a simple line in the constructor:

  WrappedTravelMethods[0].IsChecked = true; 

Markup for testing:

 <Grid Margin="5"> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition/> </Grid.RowDefinitions> <ItemsControl ItemsSource="{Binding WrappedTravelMethods}"> <ItemsControl.ItemTemplate> <DataTemplate> <RadioButton Content="{Binding Value.Name}" IsChecked="{Binding IsChecked}" GroupName="1"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> <TextBlock Grid.Row="1" Text="{Binding SelectedTravelMethod.Name}"/> </Grid> 

Travels

  • Damn, I didn’t want to, of course, introduce the IsChecked property in the VM, but if this is the only way to accomplish the current task, thanks :) - Bretbas
  • @Bretbas, you can try to write a converter, but there will be a rather cumbersome solution, so you will like it even less - Andrey NOP
  • Well, I thought about the converter myself, I even started to implement it, and I realized that there would be a full trechel :) I did not - Bretbas
  • Well, by the way, there is about the same with the code converter (see the adjacent answer). If you still make the converter a markup extension, you will still be able to throw out the addition of the resource - Andrey NOP
  • All my converters inherit from BaseConverter, which in turn implements MarkupExstention. Therefore, all the converters in my project are markup extensions :) - Bretbas

Well, for example. Should your RadioButton be selected when the current WoodVM is CurrentWood ? Well, that's fine, tie this condition to IsChecked through the converter.

Such code will turn out:

 <ItemsControl ItemsSource="{Binding Woods}"> <ItemsControl.Resources> <local:ObjectsEqualConverter x:Key="ObjectsEqualConverter"/> </ItemsControl.Resources> <ItemsControl.ItemTemplate> <DataTemplate> <RadioButton Content="{Binding Name}" GroupName="WoodGroup" FontSize="10" Command="{Binding DataContext.RequestWoodChange, RelativeSource={RelativeSource FindAncestor, AncestorType=ItemsControl}}" CommandParameter="{Binding}"> <RadioButton.IsChecked> <MultiBinding Converter="{StaticResource ObjectsEqualConverter}" Mode="OneWay"> <Binding Mode="OneWay"/> <Binding Path="DataContext.CurrentWood" RelativeSource="{RelativeSource FindAncestor, AncestorType=ItemsControl}"/> </MultiBinding> </RadioButton.IsChecked> </RadioButton> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl> 

The converter is very simple:

 class ObjectsEqualConverter : IMultiValueConverter { public object Convert(object[] values, Type tt, object p, CultureInfo ci) => values[0] == values[1]; public object[] ConvertBack(object value, Type[] tt, object p, CultureInfo ci) => throw new NotImplementedException(); } 

Just do not forget in your VM to assign the initial value to the CurrentWood property.

I got it at the start:

and I still know rosewood, here!


Just in case, my VM classes:

 class MainVM : VM // реализует INotifyPropertyChanged { public IEnumerable<WoodVM> Woods { get; } = new[] { new WoodVM("Дуб"), new WoodVM("Сосна"), new WoodVM("Кедр"), new WoodVM("Ель"), new WoodVM("Ольха") }; // начальное значение для теста WoodVM currentWood; public WoodVM CurrentWood { get => currentWood; set => Set(ref currentWood, value); } public ICommand RequestWoodChange { get; } public MainVM() { RequestWoodChange = new SimpleCommand<WoodVM>(v => CurrentWood = v); CurrentWood = Woods.First(); } } class WoodVM : VM { public WoodVM(string name) => Name = name; public string Name { get; } } 
  • Can you tell us more about linking your converter to xaml? I don't understand what <Binding Mode="OneWay"/> and `<Binding Path =" DataContext.CurrentWood "RelativeSource =" {RelativeSource FindAncestor, AncestorType = ItemsControl} "/>` mean. Well, you can not say that I do not understand at all, somewhere I guess of course. But still I would like to hear :) - Bretbas
  • one
    @Bretbas: Well, do you know what MultiBinding ? This is the kind of thing that binds several VM values ​​to a single property. These same VM values ​​are set by bindings. Well, <Binding/> is like usual binding to the current DataContext 's. Okay? And for a binding, you can specify that it is one-way ( Mode="OneWay" ), only from VM to V. Why is one-way binding? Well, so that if IsChecked changes, so that the framework does not try to assign values ​​back to VM properties via ConvertBack . - VladD
  • one
    @Bretbas: And the second part is no different from your Command binding. Well, only written not in the form of an attribute, but in the form of a nested tag (but this is the same). - VladD
  • thanks a lot :) - Bretbas
  • @Bretbas: Please! - VladD