I want to display a list of ComboBoxes, each ComboBox gets its master list from its parent DataContext but its value is bound to list that drives the list of ComboBoxes
Essentially I have a master list of items (AvailableRegisters). I then display a list of ComboBoxes which basically equate to the currently selected Registers
What works
I can see the correct number of ComboBoxes per SelectedRegisters
What doesnt work
When I select another item from the combobox I do not receive a CollectionChanged event
ViewModel Code:
Here is my (shortened) ViewModel code
public class DialogViewModel : ViewModelBase
{
public ObservableCollection<IOViewModel> InputOutputList { get; set; }
public ObservableCollection<AvailableRegister> AvailableRegisters { get; set; }
}
public class IOViewModel
{
public ObservableCollection<AvailableRegister> SelectedRegisters { get; set; }
public IOViewModel()
{
this.Registers = new ObservableCollection<AvailableRegister>();
this.Registers.CollectionChanged += Registers_CollectionChanged;
}
void Registers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Never hit when changing combo box
}
}
XAML
<ItemsControl Grid.Column="4" ItemsSource="{Binding SelectedRegisters, Mode=TwoWay}" ItemTemplate="{StaticResource ItemTemplate}"></ItemsControl>
<DataTemplate x:Key="ItemTemplate">
<ComboBox SelectedValue="{Binding Path=DataContext, RelativeSource={RelativeSource Self},Mode=TwoWay}" ItemsSource="{Binding DataContext.AvailableRegisters, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate><!-- Display it here --></DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
The problem is you are only selecting an item from the ComboBox from your ObservableCollection. You aren't changing the collection itself.
If you want, create a property on your ViewModel called SelectedItem then bind your SelectedItem to it.
public class IOViewModel
{
public ObservableCollection<AvailableRegister> SelectedRegisters { get; set; }
private AvailableRegister _selectedRegister;
public AvailableRegister SelectedRegister { get { return _selectedRegister; } set { _selectedRegister = value; } }
public IOViewModel()
{
this.Registers = new ObservableCollection<AvailableRegister>();
this.Registers.CollectionChanged += Registers_CollectionChanged;
}
void Registers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Never hit when changing combo box
}
}
<DataTemplate x:Key="ItemTemplate">
<ComboBox SelectedItem="{Binding DataContext.SelectedItem, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}" ItemsSource="{Binding DataContext.AvailableRegisters, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Mode=TwoWay}">
<ComboBox.ItemTemplate>
<DataTemplate><!-- Display it here --></DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DataTemplate>
Related
I have a List of objects of type MenuModel called MenuList inside my ViewModel. I am using CaliburnMicro framework
I would like to show this list as a list of ToggleButtons that have IsChecked property bound to other object list called SelectedMenusMonday, which is list of type SelectedMenuModel that has only IsSelected property and is the same length as MenuList.
MenuModel looks like this:
public class MenuModel
{
public int MenuKey { get; set; }
public string MenuName { get; set; }
public string Description { get; set; }
}
MenuList:
public List<MenuModel> MenuList
{
get { return _MenuList; }
set => Set(ref _MenuList, value);
}
SelectedMenuModel
public class SelectedMenuModel
{
public bool IsSelected { get; set; }
}
And SelectedMenusMonday list:
private BindableCollection<SelectedMenuModel> _SelectedMenusMonday = new BindableCollection<SelectedMenuModel>();
public BindableCollection<SelectedMenuModel> SelectedMenusMonday
{
get { return _SelectedMenusMonday; }
set => Set(ref _SelectedMenusMonday, value);
}
I am trying to display like this:
<ItemsControl x:Name="MondayMenuList" ItemsSource="{Binding MenuList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding MenuName}" IsChecked="{Binding Path=DataContext.SelectedMenusMonday.IsSelected, RelativeSource={RelativeSource AncestorType={x:Type Window}}}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The MenuList and SelectedMenus Monday get filled from SQL DB. This is the solution i tried, but it does not work. Can someone help me please! I want the ToggleButtons to be "checked" if the item on the SelectedMenusMonday have IsSelected property as true.
Thank you very much!
Name the root element in your view (or wherever you know the DataContext to be correct) and use ElementName binding as shown here:
<UserControl x:Name="view">
<Grid>
<ItemsControl x:Name="MondayMenuList" ItemsSource="{Binding MenuList}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<ToggleButton Content="{Binding MenuName}" IsChecked="{Binding ElementName=view, Path=DataContext.SelectedMenusMonday}">
</ToggleButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
Note the x:Name="view" in the UserControl element.
I have seen a couple of posts in this site similar to my issue like this one and this one But I haven't gotten this to work for me.
I have a datagrid bound to by a list of objects of type Foo and I have a combobox added for each row. The ComboBox ItemSource is not a part of the Foo class but rather it's created in the view model. I know doing so means that this combobox is the same for every row but isn't there a way in my Xaml to filter SelectedItem to just the row?
Here is my Xaml:
<DataGridTemplateColumn Header="Foo Column" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Mode=OneWay,Path=DataContext.FooCollection,
RelativeSource= {RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}"
SelectedItem="{Binding DataContext.SelectedComboBoxItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged,
RelativeSource= {RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Here is my ViewModel:
public ObservableCollection<string> FooCollection
{
get
{
return _FooCollection;
}
set
{
if (_FooCollection != value)
{
_FooCollection = value;
RaisePropertyChanged(nameof(FooCollection));
}
}
}
private ObservableCollection<string> _FooCollection = new ObservableCollection<string>();
public string SelectedComboBoxItem
{
get
{
return __SelectedComboBoxItem;
}
set
{
if (_SelectedComboBoxItem != value)
{
_SelectedComboBoxItem = value;
RaisePropertyChanged(nameof(SelectedComboBoxItem));
}
}
}
private string _SelectedComboBoxItem = string.Empty;
I am seeing my combobox collection populated but when I make a selection every other combobox gets the same value. Can anyone help me understand what I am doing wrong? Many thanks.
To make your code working you'll need to bind the SelectedComboBoxItem to DataGrid item. In your case this is a Foo type
I have a datagrid bound to by a list of objects of type Foo
Place this code to Foo class
public string SelectedComboBoxItem
{
get
{
return __SelectedComboBoxItem;
}
set
{
if (_SelectedComboBoxItem != value)
{
_SelectedComboBoxItem = value;
RaisePropertyChanged(nameof(SelectedComboBoxItem));
}
}
}
private string _SelectedComboBoxItem = string.Empty;
and update your binding according that
SelectedItem="{Binding SelectedComboBoxItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
I have a few combo boxes and columns bound to a grid, when the user
changes the selection in the combo box or edits a field. I would like
to capture the changes in my view model. The grid is bound to an
ObservableCollection
T has INotifyPropertyChanged, and currently I am capturing the changes
in the class T
<c1:C1FlexGrid
Name="grdInteractions"
ItemsSource="{Binding Interactions, Mode=TwoWay}"
>
<c1:C1FlexGrid.Columns>
<c1:Column
Width="80"
Header="Code"
ColumnName="InteractionCode"
HorizontalAlignment="Left"
IsReadOnly="true"
Binding="{Binding Parent}"
/>
<c1:Column
Width="65" Header="Strategy"
>
<c1:Column.CellTemplate>
<DataTemplate>
<ComboBox
Name="cbStrategy"
DisplayMemberPath="StrategyDesc"
SelectedValuePath="StrategyCode"
ItemsSource="{Binding Strategies}"
SelectedItem="{Binding StrategyItem, Mode=TwoWay}"
/>
</DataTemplate>
</c1:Column.CellTemplate>
</c1:Column>
</c1:C1FlexGrid.Columns>
</c1:C1FlexGrid>
C#
public class Interaction : INotifyPropertyChanged
{
public string InteractionCode { get; set; }
public List<Strategy> Strategies
{
get
{
return _strategies;
}
set
{
_strategies = value;
}
}
public Strategy StrategyItem
{
get
{
return _strategyItem;
}
set
{
_strategyItem = value;
OnPropertyChanged("StrategyItem");
}
}
}
my question here is how to know which button is clicked. My buttons are bound to property of type ObservableCollection which contains objects of type Item and I need to use that object in my ViewModel when a button is clicked. Any ideas how to know which button is clicked? I had few ideas, like sending multiple Command Parameters (1.SelectedItems from ListBox 2.The Object from the button) or bind the object from the button to another property of type Item in the ViewModel after the button is clicked in order to use it. Any ideas will be apreciated.
I have this DataTemplate for buttons
<DataTemplate x:Key="ButtonTemplate">
<WrapPanel>
<Button x:Name="OrderButton"
FontSize="10"
Height="80" Width="80"
Content="{Binding Name}"
Command="{Binding OrderCommand,
Source={StaticResource OrderViewModel}}"
CommandParameter="{Binding ElementName=ListBoxUserControl, Path=SelectedItems}">
</Button>
</WrapPanel>
</DataTemplate>
My ViewModel
public class OrderViewModel : ObservableCollection<Order>, INotifyPropertyChanged
{
public CreateOrderCommand CreateOrderCommand { get; set; }
public ObservableCollection<Item> Data { get; set; }
public OrderViewModel()
{
this.CreateOrderCommand = new CreateOrderCommand(this);
DataObservableCollection data= new DataObservableCollection();
Data = data;
}
}
And I populate my buttons like this
<WrapPanel x:Name="OrderButtons">
<ItemsControl ItemTemplate="{StaticResource ButtonTemplate}"
ItemsSource="{Binding Data, Source={StaticResource OrderViewModel}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal">
</WrapPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</WrapPanel>
Simply change the Button.CommandParameter binding to CommandParamter="{Binding}" if you want the data context of the button (i.e. the item from your items source) as the command parameter or,
CommandParameter="{Binding RelativeSource={RelativeSource Self}}" if you want the actual button that was clicked.
First send the Button DataContext using the CommandParameter. To send the SelectedItem of your Listbox you can use
<Listbox SelectedItem="{Binding SelectedItem}"/>
in your Listbox and make a SelectedItem property in your ViewModel.
private YourItemObject mySelectedItem;
public YourItemObject SelectedItem
{
get { return mySelectedItem; }
set
{
value = mySelectedItem
}
Now you can use the SelectedItem in your ViewModel when the Button gets clicket. If you have multiple selections it gets a little bit more tricky ;).
private ButtonClicked(Parameter object)
{
SelectedItem.UsingIt();
if(object is YourButtonDataContext){
YourButtonDataContext.UsingIt();
}
}
Update with MultiSelection:
With Multiselection you have to do your own Listbox.
public class CustomListBox : ListBox
{
public CustomListBox()
{
this.SelectionChanged += CustomListBox_SelectionChanged;
}
void CustomListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.SelectedItemsList = this.SelectedItems;
}
#region SelectedItemsList
public IList SelectedItemsList
{
get { return (IList)GetValue(SelectedItemsListProperty); }
set { SetValue(SelectedItemsListProperty, value); }
}
public static readonly DependencyProperty SelectedItemsListProperty =
DependencyProperty.Register("SelectedItemsList", typeof(IList), typeof(CustomListBox), new PropertyMetadata(null));
#endregion
}
In the ViewModel you have to have a property with the SelectedItems.
private IList mySelectedData = new List<SelectedDataObject>();
public IList SelectedData
{
get { return mySelectedData ; }
set
{
if (mySelectedData != value)
{
mySelectedData = value;
RaisePropertyChanged(() => SelectedData);
}
}
}
The XAML Looks like this:
<local:CustomListBox ItemsSource="{Binding YourList}" SelectionMode="Extended" SelectedItemsList="{Binding SelectedData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
...
</local:CustomListBox>
Source for Multiselection in DataGrid is: https://stackoverflow.com/a/22908694/3330227
I have a bound property on a class, Foo which is defined similar to as follows (edited for clarity),
public class Foo : INotifyPropertyChanged
{
public Foo()
{
// This should notify when IsHidden changes?
MyApp.ViewModel.HiddenCategories.CollectionChanged += (s, e) => {
this.NotifyPropertyChanged("IsHidden");
};
}
public CategoryId Category { get; set; }
// IsHidden depends on a `global' ObservableCollection object on the ViewModel
public bool IsHidden
{
get { return MyApp.ViewModel.HiddenCategories.Contains(this.Category); }
}
// IsHidden is toggled by adjusting the global ObservableCollection - how to notify the UI?
public void ToggleHidden()
{
if (this.IsHidden)
MyApp.ViewModel.HiddenCategories.Remove(this.Category);
else
MyApp.ViewModel.HiddenCategories.Add(this.Category);
}
#region INotifyPropertyChanged members
...
}
The ViewModel has the following defined on it,
public class FooRegion
{
public string RegionName { get; set; }
// Foos is bound in the top ListBox DataTemplate
// Each Foo has properties bound in the sub ListBox DataTemplate
public ObservableCollection<Foo> Foos { get; set; }
}
// This is actually what is bound to the top level ListBox
public ObservableCollection<FooRegion> FoosByRegion { get; set; }
public ObservableCollection<CategoryId> HiddenCategories { get; set; }
My XAML defines two ItemTemplates in the resources as follows,
<phone:PhoneApplicationPage.Resources>
...
<DataTemplate x:Key="MainItemTemplate">
<StackPanel >
<TextBlock Text="{Binding Name}"/>
<ListBox ItemsSource="{Binding Foos}"
ItemTemplate="{StaticResource SubItemTemplate}" />
</StackPanel>
</DataTemplate>
...
<DataTemplate x:Key="SubItemTemplate">
<StackPanel Opacity="{Binding IsHidden, Converter={StaticResource BoolToOpacity}}" >
<toolkit:ContextMenuService.ContextMenu>
<toolkit:ContextMenu>
<toolkit:MenuItem Header="{Binding IsHidden, ConverterParameter=unhide foo|hide foo,
Converter={StaticResource BoolToStrings}}" Tap="toggleHideFooContextMenuItem_Tap" />
</toolkit:ContextMenu>
</toolkit:ContextMenuService.ContextMenu>
<TextBlock Text="Some Text Here"/>
</StackPanel>
</DataTemplate>
...
</phone:PhoneApplicationPage.Resources>
These resources are called on to a 'nested' ListBox as follows,
<ListBox ItemTemplate="{StaticResource MainItemTemplate}" ItemsSource="{Binding FoosByRegion}" />
This method appears to only work piecemeal, Some Foo objects are updated in the UI, but others are not - as if the notification is not reaching the UI.
How should I be tackling this problem?
ContextMenu from the Windows Phone Toolkit applies an animation which affects the opacity of the surrounding elements. Applying the opacity to the child elements individually solved the problem.