I have a WPF ComboBox:
<ComboBox ... ItemsSource="{Binding Source={StaticResource viewModel}, Path=getItems, Mode=OneTime}" x:Name="combobox" SelectionChanged="combobox_SelectionChanged">
...
</ComboBox>
with lots of items.
And my ViewModel class:
public class ViewModel
{
private readonly ObservableCollection<ObjectA> _objectACollection= new ObservableCollection<ObjectA>();
public ViewModel()
{
_objectACollection.Add(new ObjectA("Text 1", "Text", "Text"));
_objectACollection.Add(new ObjectA("Text 2", "Text", "Text"));
_objectACollection.Add(new ObjectA("Text 3", "Text", "Text"));
}
public void combobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Trace.WriteLine(combobox.SelectedIndex);
}
public ObservableCollection<ObjectA> getItems
{
get { return _objectACollection; }
}
}
and the selectionChanged listener:
private void combobox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Trace.WriteLine(combobox.SelectedIndex);
}
The ComboBox is displayed and when I choose something I get the index of the collection objects.
But is there any way to return me the object? for example:
I select the first element in the ComboBox(index 0),
how can I get (in the combobox_SelectionChanged listener) the object from the _objectACollection with index 0?
There is SelectedItem property of ComboBox. I think that you can bind SelectedItem with TwoWay with your VM. Following is exmaple. I hope that this help.
<ComboBox ... ItemsSource="{Binding Source={StaticResource viewModel}, Path=getItems, Mode=OneTime}" x:Name="combobox" SelectedItem="{Binding SelectedObjectA, Mode=TwoWay}">
...
</ComboBox>
You should add SelectedObjectA property in your VM. You can get selected item from VM.SelectedObjectA property.
private ObjectA _SelectedObjectA;
public ObjectA SelectedObjectA
{
get
{
return _SelectedObjectA;
}
set
{
if (_SelectedObjectA == value)
return;
_SelectedObjectA = value;
// Notifu changed here
}
}
You can use combobox.SelectedItem.
Maybe you could try using a collection that implements ICollectionView interface, I am sure there are a few baked in. It keeps track of the selected item in your collection for you without the need for a separate SelectedObjectA property on your viewmodel. So you can have:
> <ComboBox ... ItemsSource="{Binding Source={StaticResource viewModel},
> Path=**SomeICollectionView**, Mode=OneTime}" x:Name="combobox"> ...
> </ComboBox>
to get the selected item from the viewmodel class all you have to do is SomeICollectionView.CurrentItem
Related
I am writing app using MVVM pattern in C#.
My goal is to get selected items from ListBox in my own User Control.
I have created bindable object, with method to change this object (called when something new is selected):
public partial class MyUserControl : UserControl
{
...
public IEnumerable SelectedItems
{
get { return (IEnumerable)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IEnumerable),
typeof(MyUserControl),
new FrameworkPropertyMetadata(default(IEnumerable),
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public MyUserControl ()
{
InitializeComponent();
}
private void SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItems = ListBox.SelectedItems;
}
}
There is also Items part, and in xaml part ListBox is named ListBox:
<ListBox Name="ListBox" SelectionChanged="SelectionChanged" ... />
This how it looks in page with ViewModel, in which MyUserControl is created:
<uc:MyUserControl ... SelectedItems="{Binding Path=MyObjectItems}" />
And here comes the problem. When setting SelectedItems in ViewModel:
private ObservableCollection<MyObject> _myObjectItems;
public ObservableCollection<MyObject> MyObjectItems
{
get { return _myObjectItems; }
set { _myObjectItems = value; }
}
No matter what I do, value will always be null. This also means, that SelectedItems in MyUserControl is null, too.
I can, for example use OneWayToSource binding mode:
<uc:MyUserControl ... SelectedItems="{Binding Path=MyObjectItems, Mode=OneWayToSource}" />
value is still null, same as MyObjectItems, but at least SelectedItems in MyUserControl contains selected items. Not good enough :/
After hours of trying different approaches I've found NuGet package Extended WPF Toolkit. List SelectedItemsOverride from class CheckListBox allows to bind list of selected items:
<UserControl x:Class="View.UserControls.MyUserControl"
...
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
x:Name="Root">
<Grid>
<xctk:CheckListBox Name="ListBox"
ItemSelectionChanged="SelectionChanged"
SelectedItemsOverride="{Binding SelectedItems, ElementName=Root}"
... />
</Grid>
</UserControl>
And because of that, binding:
<uc:MyUserControl ... SelectedItems="{Binding Path=MyObjectItems}" />
works! I have access to selected items in View Model - everything in simple way.
I have a model class, Book, which contains a Keywords property:
public class Book : INotifyPropertyChanged
{
private ObservableCollection<string> _keywords;
...
public ObservableCollection<string> Keywords
{
get => _keywords;
set
{
_keywords = value;
OnPropertyChanged("Keywords");
}
}
}
and in my MainPage I have 2 components : a list View and a combobox whose each entry is a checkBox:
<ComboBox
x:Name="cbb_Keywords"
Grid.Column="2"
Width="300"
Margin="5,0,0,0"
HorizontalAlignment="Left"
ItemsSource="{Binding Source={StaticResource AllBooks}}"
DataContext="{Binding ElementName=listBoxBooks,Path=SelectedItem,UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Width="200" Content="{Binding}" Click="ButtonBase_OnClick">
<CheckBox.IsChecked>
<MultiBinding Converter="{StaticResource TextInListTrueFalseConverter}" Mode="OneWay">
<Binding ElementName="listBoxBooks" Path="SelectedItem.KeywordsForTextbox" Mode="OneWay"></Binding>
<Binding RelativeSource="{RelativeSource Self}" Path="Content"></Binding>
</MultiBinding>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
the checkBox.IsChecked multibinding is oneway, and when I click on a checkbox, it calls this method:
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
var content = (string)cb.Content;
var keywords = ((Book)listBoxBooks.SelectedItem).Keywords;
bool clicked = cb.IsChecked.Value;
if (clicked)
keywords.Add(content);
else
keywords.Remove(content);
}
it works more or less but there are 2 caveats:
sometimes the checkbox on which I just clicked is displayed in the combobox's checkbox, which is not expected and is annoying
I have, in addition of the combobox, an other component, a textbox, which contains the list of the keywords for the listview's selectedItem:
but when I click on a checkbox to toogle this, the listbox containing the list is not refreshed...
so I chenged a little my Keywords property, in Book:
public ObservableCollection<string> Keywords
{
get => _keywords;
set
{
_keywords = value;
OnPropertyChanged("Keywords");
OnPropertyChanged("KeywordsForTextbox");
}
}
and the KeywordsForTextbox property is like this:
public string KeywordsForTextbox
{
get { return string.Join(",", _keywords); }
}
finally, to be complete, here is the textBox component in my MainWindow:
<TextBox x:Name="txb_Keywords"
Grid.Column="1"
Width="500"
Text="{Binding ElementName=listBoxBooks,Path=SelectedItem.KeywordsForTextbox,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}" />
why does the checkbox appears in the combobox's textbox? why isn't refreshed the other textbox?
thank you.
The problem is that when modifying the Keywords collection the actual Keywords property doesn't change. It's still the same collection object. Only the object's properties (Items) change.
In your Book class you could use methods to do the adding, and removing, then notify property changed from there.
public void AddKeyword(string name)
{
Keywords.Add(name);
OnPropertyChanged("Keywords");
}
public void RemoveKeyword(string name)
{
Keywords.Remove(name);
OnPropertyChanged("Keywords");
}
Then change your event like this.
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
CheckBox cb = (CheckBox)sender;
var content = (string)cb.Content;
var book = ((Book)listBoxBooks.SelectedItem);
bool clicked = cb.IsChecked.Value;
if (clicked)
book.AddKeyword(content);
else
book.RemoveKeyword(content);
}
I found some topics "How to bind a combobox from enum list", but my problem is that I try to use MVVM arhitecture and let my View (xaml) clear as you see below:
part of View (xaml.cs):
public partial class GreenCertificatesStockForm : Erp.Core.Wpf.BaseWindow
{
private Models.GreenCertificatesGroupModel model;
public GreenCertificatesStockForm()
{
model = new Models.GreenCertificatesGroupModel();
this.DataContext = model;
InitializeComponent();
model.LoadForm(); // propose some dates for my form
model.RequestClose += () => { Close(); };
}
}
part of View (xaml) my RadComboBox:
<telerik:RadComboBox Name="certificatesTypeRadComboBox"
Margin="5 2 0 2" Width="150"
SelectedValue="{Binding CertificatesTypeEnum , Mode=TwoWay,
ValidatesOnDataErrors=True,
ValidatesOnExceptions=True,
NotifyOnValidationError=True}"
ItemSource="{Binding }"
SelectedItem="{Binding }"
telerik:StyleManager.Theme="Office_Blue" BorderBrush="#FF707070" Background="#FFDDDDDD"
>
</telerik:RadComboBox>
So, my Enum list is created in my ViewModel (class.cs) as:
public enum CertificatesTypeEnum {
Estimat = 1,
Calculat = 2,
Facturat = 3
}
I need to display in my RadComboBox all Values of the Enum and by selectedValue to save the specific Key of selection in DB (this with a parameter). How can I display the values from ViewModel into ComboBox (View)?
I know can do something like :
var items = Enum.GetValues(typeof(CertificatesTypeEnum)).Cast<CertificatesTypeEnum>().Select(i => new ComboboxItem()
{ Text = Enum.GetName(typeof(gender), i), Value = (int)i}).ToArray<ComboboxItem>();
//Add the items to your combobox (given that it's called comboBox1)
RadComboBoxName.Items.AddRange(items);
but this must be made in xaml.cs file (and I don't want this solution), because in ViewModel the combobox are not recognised and will be not found.
In short : display Values of Enum list from ViewModel class in xaml file.
Why don't you just call the Enum.GetValues method in the view model? This is MVVM:
public class GreenCertificatesGroupModel
{
public IEnumerable<CertificatesTypeEnum> EnumValues
{
get
{
var list = Enum.GetValues(typeof(CertificatesTypeEnum)).Cast<CertificatesTypeEnum>();
return list;
}
}
private CertificatesTypeEnum _selectedItem;
public CertificatesTypeEnum SelectedItem
{
get { return _selectedItem; }
set { _selectedItem = value; }
}
}
XAML:
<telerik:RadComboBox ItemsSource="{Binding EnumValues}" SelectedItem="{Binding SelectedItem, Mode=TwoWay}" />
In my View I have a DataGrid which stores objects of 2 descending types. Every row has a Button with a Command connected to the ViewModel. In the ViewModel I need to find out which type of object has been chosen.
The question is what is the best and simple way of accessing SelectedItem property of the DataGrid from the Execute command method in a ViewModel?
So far I did it like this:
var window = Application.Current.Windows.OfType<Window>()
.SingleOrDefault(x => x.IsActive);
var dataGrid = (DataGrid) window.FindName("MyGridName");
...
UPDATE - Xaml:
<DataGrid Name="MyGridName" ItemsSource="{Binding Elements}"
AutoGenerateColumns="False" CanUserAddRows="False"
CanUserDeleteRows="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTemplateColumn Width="auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Name="OptionsBtn" Margin="5" Width="auto"
Height="30" Content="Options"
Command="{Binding ElementName=ElementsViewWindow,
Path=DataContext.ShowOptionsMenuCommand}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
If you take the right MVVM approach this is very easy to do. All you need is to define the item collection of you entities which will be bound to ItemsSource of your DataGrid and a property which will be bound to SelectedItem of your DataGrid. Then in your command you simply reference your selected item property of your model to access the selected item in your DataGrid.
Here is an example implementation with MVVM Light. First you define an observable collection of your entities:
public const string ItemsCollectionPropertyName = "ItemsCollection";
private ObservableCollection<DataItem> _itemsCollection = null;
public ObservableCollection<DataItem> ItemsCollection
{
get
{
return _itemsCollection;
}
set
{
if (_itemsCollection == value)
{
return;
}
_itemsCollection = value;
RaisePropertyChanged(ItemsCollectionPropertyName);
}
}
Then you define a property to hold the selected item:
public const string SelectedItemPropertyName = "SelectedItem";
private DataItem _selectedItem = null;
public DataItem SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem == value)
{
return;
}
_selectedItem = value;
RaisePropertyChanged(SelectedItemPropertyName);
}
}
After that you define a command to handle business logic:
private ICommand _doWhateverCommand;
public ICommand DoWhateverCommand
{
get
{
if (_doWhateverCommand == null)
{
_doWhateverCommand = new RelayCommand(
() => { /* do your stuff with SelectedItem here */ },
() => { return SelectedItem != null; }
);
}
return _doWhateverCommand;
}
}
Finally you create view elements and bind them to the ViewModel:
<DataGrid ItemsSource="{Binding ItemsCollection}" SelectedItem="{Binding SelectedItem}" AutoGenerateColumns="True" />
<Button Content="Do stuff" Command="{Binding DoWhateverCommand}" />
The question is what is the best and simple way of accessing SelectedItem property of DataGrid from Execute command function in a ViewModel?
Just add a property to the view model class where the ShowOptionsMenuCommand property is defined and bind the SelectedItem property of the DataGrid to this one:
<DataGrid Name="MyGridName" ItemsSource="{Binding Elements}" SelectedItem="{Binding SelectedElement}" ... >
Then you can access the source property (SelectedElement or whatever you choose to call it) directly from the Execute method.
The other option would be to pass the item as a CommandParameter to the command:
<Button Name="OptionsBtn" ... Content="Options"
Command="{Binding ElementName=ElementsViewWindow, Path=DataContext.ShowOptionsMenuCommand}"
CommandParameter="{Binding}" />
I am trying to use an attached behavior to execute a command in my ViewModel when the user Double Clicks on the list item.
I have reviewed a number of articles on the subject, and tried creating a simple test application but am still having problems eg.
Firing a double click event from a WPF ListView item using MVVM
My simple test ViewModel has 2 collections, one that returns a list of strings and the other that returns a List of ListViewItem types
public class ViewModel
{
public ViewModel()
{
Stuff = new ObservableCollection<ListViewItem>
{
new ListViewItem { Content = "item 1" },
new ListViewItem { Content = "item 2" }
};
StringStuff = new ObservableCollection<string> { "item 1", "item 2" };
}
public ObservableCollection<ListViewItem> Stuff { get; set; }
public ObservableCollection<string> StringStuff { get; set; }
public ICommand Foo
{
get
{
return new DelegateCommand(this.DoSomeAction);
}
}
private void DoSomeAction()
{
MessageBox.Show("Command Triggered");
}
}
Here is the attached property which is like may other examples you see:
public class ClickBehavior
{
public static DependencyProperty DoubleClickCommandProperty = DependencyProperty.RegisterAttached("DoubleClick",
typeof(ICommand),
typeof(ClickBehavior),
new FrameworkPropertyMetadata(null, new PropertyChangedCallback(ClickBehavior.DoubleClickChanged)));
public static void SetDoubleClick(DependencyObject target, ICommand value)
{
target.SetValue(ClickBehavior.DoubleClickCommandProperty, value);
}
public static ICommand GetDoubleClick(DependencyObject target)
{
return (ICommand)target.GetValue(DoubleClickCommandProperty);
}
private static void DoubleClickChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ListViewItem element = target as ListViewItem;
if (element != null)
{
if ((e.NewValue != null) && (e.OldValue == null))
{
element.MouseDoubleClick += element_MouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
element.MouseDoubleClick -= element_MouseDoubleClick;
}
}
}
static void element_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
UIElement element = (UIElement)sender;
ICommand command = (ICommand)element.GetValue(ClickBehavior.DoubleClickCommandProperty);
command.Execute(null);
}
}
In my main window, I have defined the style which sets the attached behaviour and binds to the Foo command
<Window.Resources>
<Style x:Key="listViewItemStyle" TargetType="{x:Type ListViewItem}">
<Setter Property="local:ClickBehavior.DoubleClick" Value="{Binding Foo}"/>
</Style>
</Window.Resources>
Works fine when ListViewItems are defined:
<!-- Works -->
<Label Grid.Row="2" Content="DoubleClick click behaviour:"/>
<ListView Grid.Row="2" Grid.Column="1" ItemContainerStyle="{StaticResource listViewItemStyle}">
<ListViewItem Content="Item 3" />
<ListViewItem Content="Item 4" />
</ListView>
This works too, when bound to the list of type ListViewItem:
<!-- Works when items bound are of type ListViewItem -->
<Label Grid.Row="3" Content="DoubleClick when bound to ListViewItem:"/>
<ListView Grid.Row="3" Grid.Column="1" ItemContainerStyle="{StaticResource listViewItemStyle}" ItemsSource="{Binding Stuff}">
</ListView>
But this doesn't:
<!-- Does not work when items bound are not ListViewItem -->
<Label Grid.Row="4" Content="DoubleClick when bound to string list:"/>
<ListView Grid.Row="4" Grid.Column="1" ItemContainerStyle="{StaticResource listViewItemStyle}" ItemsSource="{Binding StringStuff}">
</ListView>
In the output window you see the error, but finding it difficult to understand what is wrong.
System.Windows.Data Error: 39 : BindingExpression path error: 'Foo' property not found on 'object' ''String' (HashCode=785742638)'. BindingExpression:Path=Foo; DataItem='String' (HashCode=785742638); target element is 'ListViewItem' (Name=''); target property is 'DoubleClick' (type 'ICommand')
So my quesion is: How can you get the Command wired up correctly to each ListViewItem when you bind your ListView to a list of Model objects?
Thanks.
The problem is that the DataContext for the Binding is the string. Since there is no Foo property of the string class, you are getting an error. This doesn't happen in the other cases because they inherit their DataContext from the parent (this doesn't happen for automatically generated containers for data items - their DataContext is the data item).
If you change your binding to use the parent ListView's DataContext, it should work fine:
Value="{Binding DataContext.Foo, RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"