I am using a Listbox in m WPF app where I tried to remove the SelectedItem so the user can reclick on it to do an action.
I have a classic ListBox :
<ListBox
x:Name="MenuItemList"
Grid.Row="1"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding MenuItems}"
SelectedItem="{Binding SelectedMenu, UpdateSourceTrigger=PropertyChanged}">
[...] </ListBox>
I have binded the SelectedMenu in my VM :
public MenuItem SelectedMenu
{
get { return null; }
set
{
MenuIsOpened = false;
DisplayedMenu = value;
OnPropertyChanged("SelectedMenu");
}
}
I tried another way with a private property where I changed it to null
private MenuItem _SelectedMenu;
public MenuItem SelectedMenu
{
get { return _SelectedMenu; }
set
{
MenuIsOpened = false;
DisplayedMenu = value;
_SelectedMenu = null;
OnPropertyChanged("SelectedMenu");
}
}
But it does not work as I want... When I click on an item, the property is null but in the view, the listbox always highlights the selected item and the second click on it doesn't trigger the property.
Heres my working example:
// in constructor or so
AddedBlocks = new ObservableCollection<string>();
// the property
public ObservableCollection<string> AddedBlocks { get; }
/// <summary>
/// Deletes selected block from view and model
/// </summary>
private void OnDeleteSelectedBlock(object parameter)
{
try
{
AddedBlocks.RemoveAt(selectedBlockIndex);
}
}
And XAML:
<ListBox ItemsSource="{Binding Blocks, Mode=OneWay}" SelectedIndex="{Binding SelectedBlockIndex, Mode=TwoWay}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete block" Command="{Binding DeleteSelectedBlock}"/>
</ContextMenu>
</ListBox.ContextMenu>
</ListBox>
See the two way binding on the selectedIndex. Maybe its easier to use that instead of selectedItem.
Hope this helps. If you dont want to use the context menu, add a button or this
<KeyBinding Key="Delete" Command="{Binding DeleteSelectedBlock}"/>
I do something else to resolve my problem.
I didn't try to change the selected item but I added a command on my Listbox
<ListBox
x:Name="MenuItemList"
Grid.Row="1"
ItemsSource="{Binding MenuItems}"
SelectedItem="{Binding SelectedMenu, UpdateSourceTrigger=PropertyChanged}">
[...]
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding CloseMenuCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
And in my MVVM, I close my menu as I want to do it at the beginning :
private void ExecuteCloseMenuCommand(object o)
{
MenuIsOpened = false;
}
In this way, the user can reclick on the item menu which is already selected and the menu will still be closed by the click.
Related
I have problem with MouseBinding in WPF.
When I right click mouse to selected item, the forms (UserControl) is open, but selected item is lost (canceled).
Can you help me, please?
XAML:
<UserControl.InputBindings>
<MouseBinding Gesture="RightClick" Command="{Binding ShowDWClickOnRightButton}"/>
</UserControl.InputBindings>
<DataGrid x:Name="DatagridForLogistic" Grid.Row="2" MinWidth="620" SelectionMode="Single" Margin="5"
Style="{StaticResource MujStyl}" FontStyle="Normal" FontWeight="Light"
ItemsSource="{Binding Stredisko,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding MyItem, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
ScrollViewer.CanContentScroll="True" HorizontalScrollBarVisibility="Visible">
Command
public ICommand ShowDWClickOnRightButton
{
get
{
return new RelayCommand(() =>
{
VMRightButton viewModel = UnityActivator.CreateInstance<VMRightButton>();
viewModel.Title = String.Format("Action on right click for card {0}", MyItem.CardId).ToString();
viewModel.NumberOfCard = MyItem.CardId;
CreateDWRightButton.Raise(new Notification() { ViewModel = viewModel });
});
}
}
Properties MyItem
public ENotifiedProperties MyItem
{
get
{
return _entityProperties;
}
set
{
if (_entityProperties != value)
{
_entityProperties = value;
NotifyPropertyChanged("MyItem");
}
}
}
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'm trying to make a DataGrid where the user can edit data and apply oder cancel changes afterwards.
Moreover one DataGridCell has a TemplateSelector to help the user to enter just the valid data. (DateTime, Boolean,...)
My Model has some properties. For my problem there are two relevant:
Model.Type and Model.Value
In CodeBehind my properties have the type:
enum Type;
string Value;
When the user edits the Value the TemplateSelector should get the right DataTemplate depending on Type
My XAML looks like this:
<DataGrid x:Name="datagrid"
ItemsSource="{Binding Variables}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AutoGenerateColumns="False"
IsSynchronizedWithCurrentItem="True">
<DataGrid.Columns>
<DataGridComboBoxColumn Header="{StaticResource VariablesType}"
ItemsSource="{Binding Source={StaticResource VariableTypes}, Mode=OneWay}"
SelectedItemBinding="{Binding Type}"
Width="80"/>
<DataGridTemplateColumn Header="{StaticResource VariablesValue}" Width="2*">
<DataGridTemplateColumn.CellEditingTemplateSelector>
<TemplateSelector:TemplateSelector_Variables>
<!--Definitin of Templates,..-->
</TemplateSelector:TemplateSelector_Variables>
</DataGridTemplateColumn.CellEditingTemplateSelector>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Value}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Let's get to the TemplateSelector:
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item == null)
return base.SelectTemplate(item, container);
RaVariableType VariablenType = ((RaVariable)item).Type;
//SelectingLogic
//Here I would need the CURRENT type but it gives me the type of my Model
}
I have alread had a solution:
If I use:
SelectedItemBinding="{Binding Type, UpdateSourceTrigger="PropertyChanged"}"
It works BUT I would like to handle an OK or Cancel of the User and this code changes my Model.
Another question:
How do you handle such a decition. (How do you update the Model - code of the command)
Thank you!
Are you using MVVM pattern. You need to handle it in View Model just bind button to the SelectedItem of the DataGrid and use it whenever the Command is invoked. Ideal way to do this kind of stuff is using commands (Basically MVVM pattern) you can create a command in your Data Object(ViewModel) and call Button.Command , So that there wont be any code behind like Button click.
Example is shown here
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<Button
Command="{Binding Path=DataContext.OKCommand,
RelativeSource= {RelativeSource FindAncestor,
AncestorType={x:Type DataGrid}}}">
</Button>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
View model code
RelayCommand _okCommand;
public ICommand okCommand
{
get
{
if (_ok == null)
{
_ok= new RelayCommand(param => this.Show());
}
return _ok;
}
}
private void Show()
{
if (Parent != null)
{
// Handle ok logic Here
}
}
Are you using any patterns like Ninject, Unity etc and entity framework for handling your db layer code ?. Your viewmodel will contain properties and the column names of your grid should come from model property. Once the property value changes you need to write save logic probably using EF or linq to sql db code. Save logic will be handled by seperate class and if the user clicks a button your view model should call the interface method and do the save. Instance of your class which handles save logic will be supplied by Unity or Ninject (depending on your implementation). Your dependencies will be injected into your view models, and your view models would expose properties that are bound to by the view.
For passing the selected item from grid to model
Create a Property in the ViewModel for saving the selected User: public User SelectedUser { get; set; }
Bind SelectedItem of the datagrid view to this Property: SelectedItem="{Binding SelectedUser}"
There are a few way to select items in the DataGrid. It just depends which one works best for your situation
First and most basic is SelectedIndex this will just select the Row at that index in the DataGrid
<DataGrid SelectedIndex="{Binding SelectedIndex}" />
private int _selectedIndex;
public int SelectedIndex
{
get { return _selectedIndex; }
set { _selectedIndex = value; NotifyPropertyChanged("SelectedIndex"); }
}
SelectedIndex = 2;
SelectedItem will select the row that matches the row you set
<DataGrid SelectedItem="{Binding SelectedRow}" />
private DataRow _selectedRow;
public DataRow SelectedRow
{
get { return _selectedRow; }
set { _selectedRow = value; NotifyPropertyChanged("SelectedRow");}
}
SelectedRow = items.First(x => x.whatever == something);
The most common one is SelectedValue with SelectedValuePath set, in this case you set the column you want to select with and then to can select the row by setting the corresponding value
<DataGrid SelectedValuePath="Size Quantity" SelectedValue="{Binding SelectionValue}"
private string _selectedValue
public string SelectionValue
{
get { return _selectedValue; }
set { _selectedValue = value; NotifyPropertyChanged("SelectionValue"); }
}
SelectionValue = "Blue";
XAML Example
<Window x:Class="WpfApplication21.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="202" Width="232" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<DataGrid SelectedValuePath="Size Quantity"
SelectedValue="{Binding SelectionValue}"
SelectedIndex="{Binding SelectedIndex}"
ItemsSource="{Binding SizeQuantityTable}"
AutoGenerateColumns="True"
Margin="0,0,0,41" />
<StackPanel Orientation="Horizontal" Height="37" VerticalAlignment="Bottom" >
<Button Content="SelectedIndex" Height="26" Width="107" Click="Button_Click_1"/>
<Button Content="SelectedValue" Height="26" Width="107" Click="Button_Click_2"/>
</StackPanel>
</Grid>
</Window>
I have a ListBox;
<ListBox Grid.Row="1" Grid.ColumnSpan="2"
x:Name="customerListBox"
ItemsSource="{Binding Customers}"
DisplayMemberPath="Customername"
SelectionMode="Single" Width="200"/>
Customers is public ObservableCollection<Customer> Customers { get; private set; }
Now I am binding ListBox Selected Item to a Text Box:
<TextBox Text="{Binding ElementName=customerListBox,
Path=SelectedValue.Customername,Mode=OneWay}"/>
I have made it one-way as there I want to commit the changes only on click of my Save button and not when the value change on TextBlock.
<Button Content="Save" Grid.Column="0" Grid.Row="3" Width="80" Height="30"
Command="{Binding SaveCommand}"
You're going the wrong way about it, imho.
Don't bind TextBox directly against selected item. Rather, create a new command, SelectionChangedCommand, and new property, CurrentlyActiveText, bind it against TextBox.
The logic would be simple:
SelectionChangedCommand = new RelayCommand(selectedItem=> {
// todo: ask user if he wants to commit the previous changes?!
CurrentlyActiveText = (string)selectedItem;
})
SaveCommand = new RelayCommand(() => {
yourObservable[SelectedIndex] = CurrentlyActiveText;
});
Perhaps a nicer way of doing this is using Triggers to fire a Command on the ListBox SelectionChanged event. Putting logic in a property setter always feels a bit wrong to me
<ListBox...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding CustomerListBoxSelectionChanged}" CommandParameter="{Binding ElementName=customerListBox,Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
In your view model:
public Customer CurrentCustomer { get; set; }
public RelayCommand<Customer> CustomerListBoxSelectionChanged { get; set; }
private void OnCustomerListBoxSelectionChanged(Customer customer)
{
CurrentCustomer = customer;
NewCustomerName = customer.CustomerName;
}
private string _newCustomerName;
public string NewCustomerName
{
get { return _newCustomerName; }
set
{
if (_newCustomerName == value)
return;
_newCustomerName = value;
RaisePropertyChanged("NewCustomerName");
}
}
Your TextBox in your XAML becomes:
<TextBox Text="{Binding NewCustomerName}"/>
And finally, your SaveCommand calls a method that simply does...
private void OnSave()
{
CurrentCustomer.CustomerName = NewCustomerName;
}
Note you will also need to make sure that your CustomerName in your Customer object is raising PropertyChanged events in order to reflect the update in your ListBox
Note that doing it this way also saves you a futher lookup the ObservableCollection to perform an update. It'll save you some time - any performance gain is always good :)
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>