MouseDoubleClick on ListItem with MVVM in WPF - wpf

I want to show dialog when i double click any of the list items. But the flow of the program never go in the ShowTextCommand property. I get the list of the names (that works fine) but I can't get the dialog. This is my XAML:
<ListView ItemsSource="{Binding List}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding ShowTextCommand, UpdateSourceTrigger=PropertyChanged}"></MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
this is my Command class:
public class EnterTextCommand : ICommand
{
public EnterTextCommand(TekstoviViewModel vm)
{
ViewModel = vm;
}
private TekstoviViewModel ViewModel;
#region ICommand interface
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
// return ViewModel.CanExecute;
}
public void Execute(object parameter)
{
ViewModel.EnterText();
}
#endregion
}
and the view model
private ICommand command;
public ICommand ShowTextCommand
{
get
{
if (command == null)
command = new EnterTextCommand(this);
return command;
}
internal void EnterText()
{
MessageBox.Show("Event Success");
}
Can someone help ?

Your DataTemplate can't find the command, specify the full path to it using ElementName Binding
<ListView ItemsSource="{Binding List}" x:Name="MainList">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.ShowTextCommand, UpdateSourceTrigger=PropertyChanged,ElementName=MainList}"></MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>

Related

Create a CheckBox that will depend on other CheckBoxes that are inside the ItemsControl

How can I make the CheckBox above (All) depend on the other three in a good way.
That is, if they are all Checked, then it will be Checked, if all are UnChecked, then it will be UnChecked,
And if some of them are Checked, then it will be Null
public ObservableCollection<string> Items { get; set; } = new() { "A", "B", "C" };
<StackPanel>
<CheckBox Content="All"/>
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox/>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Iterating through UI is a bad idea, it is complicated. You should use mvvm and work with data rather than relying on ui as a data store.
How would that work?
You should bind a collection of item viewmodels. A class exposing both a bool ischecked property and a string description.
I am using the CommunityToolkit.Mvvm package here. That generates code including public properties using attributes.
Here's my viewmodel for each item
using CommunityToolkit.Mvvm.ComponentModel;
namespace WpfApp3
{
public partial class ItemViewModel : ObservableObject
{
[ObservableProperty]
private bool? isChecked;
[ObservableProperty]
private string description;
public ItemViewModel(bool? _isChecked, string _description)
{
isChecked = _isChecked;
description = _description;
}
}
}
I want to bind both those properties and I'll need a window viewmodel exposes a public property for those items.
I'll create a mainwindowviewmodel, but here's the view:
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<CheckBox Content="All"
IsChecked="{Binding AllChecked, Mode=TwoWay}"
/>
<ItemsControl ItemsSource="{Binding Items}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Content="{Binding Description}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</Window>
All is bound to a property in my mainwindowviewmodel.
Here's my mainwindowviewmodel:
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
namespace WpfApp3
{
public partial class MainWindowViewModel : ObservableObject
{
[ObservableProperty]
private bool? allChecked = false;
[ObservableProperty]
private ObservableCollection<ItemViewModel> items = new ObservableCollection<ItemViewModel>
{
new ItemViewModel((bool?)null, "A"),
new ItemViewModel(true, "B"),
new ItemViewModel(false, "C"),
};
public MainWindowViewModel()
{
foreach (var item in items)
{
item.PropertyChanged += Item_PropertyChanged;
}
}
private void Item_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "IsChecked")
{
reDoAll(sender);
}
}
private void reDoAll(object? s)
{
var item = s as ItemViewModel;
Debug.WriteLine(item.Description);
if (items.All(x => x.IsChecked == true))
{
AllChecked = true;
return;
}
if (items.All(x => x.IsChecked == false))
{
AllChecked = false;
return;
}
AllChecked = (bool?)null;
}
}
}
When you change any ischecked, the property changed handler runs redDoAll which applies your logic and sets AllChecked. The All checkbox in the view responds.

Display only one TextBox which is bound to selected item from ListView

I have a ListView which is bound to an ObservableCollection
I have added a DataTemplate to a ListView to bind item with TextBox in purpose to Rename selectedItem using Rename from ContextMenu:
View
<ListView DockPanel.Dock="Left"
Background="MidnightBlue"
Width="140"
SelectedItem="{Binding SelectedNotebook, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding Notebooks}"
x:Name="notebooksList"
SelectionChanged="notebooksList_SelectionChanged"
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<uc:DisplayNotebook Notebook="{Binding}">
<uc:DisplayNotebook.ContextMenu>
<ContextMenu>
<MenuItem Header="Rename"
Command="{Binding Source={StaticResource vm}, Path=EditCommand}"
CommandParameter="{Binding SelectedNotebook}"/>
<MenuItem Header="Delete"
Command="{Binding Source={StaticResource vm}, Path=DeleteNotebookCommand}"
CommandParameter="{Binding SelectedNotebook}"/>
</ContextMenu>
</uc:DisplayNotebook.ContextMenu>
</uc:DisplayNotebook>
<TextBox Text="{Binding Name, Mode=TwoWay}"
Visibility="{Binding Source={StaticResource vm}, Path=IsVisible}"
x:Name="notebookTextBox">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostFocus">
<i:InvokeCommandAction Command="{Binding Source={StaticResource vm}, Path=EndEditingCommand}"
CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public async void StopEditingNotebook(Notebook notebook)
{
IsVisible = Visibility.Collapsed;
await DatabaseHelper.Update(notebook);
GetNotebooks();
}
public async void StopEditingNote (Note note)
{
IsVisible = Visibility.Collapsed;
await DatabaseHelper.Update(note);
GetNotes();
}
Commands
public class EndEditingCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public NotesVM ViewModel { get; set; }
public EndEditingCommand(NotesVM vm)
{
ViewModel = vm;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
Notebook notebook = parameter as Notebook;
if (notebook != null)
ViewModel.StopEditingNotebook(notebook);
}
}
public class EditCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public NotesVM ViewModel { get; set; }
public EditCommand(NotesVM vm)
{
ViewModel = vm;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ViewModel.StartEditing();
}
}
I'd like to Rename only one item at once by popping up one TextBox instead of all TextBoxes in the same time(this what is happening currently due to bound TextBox in DataTemplate).
I was wondering about finding an ID of selectedItem and then somehow display only this particular TextBox.
Do you have any ideas on this matter?
Thanks for help in advance
You can use a MultiDataTrigger to toggle the Visibility of the TextBox. The conditions are IsSelected and IsVisible from your view model.
Alternatively (recommended), spend your item model an IsEditEnabled property (or move and rename the IsVisible property to the item model).
Currently, every item binds to the same IsVisible property of the same ViewModel instance.
Instead, each item must bind to its own property (that's why the property should be defined in the item model).
Note, a member named with the prefix "is" is expected to return a boolean.
In your case it would be semantically correct to name the property Visibility and not IsVisible.
Additionally, the CommandParameter binding inside the ContextMenu looks wrong too. It must be
<ContextMenu>
<MenuItem CommandParameter="{Binding}"/>
</ContextMenu>

WPF: bind my Checkbox IsChecked event to my model

So i have this ListView with ItemSource of my object and ListViewItem:
<GridViewColumn Header="Select" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Command="{Binding SelectedInterfaceCommand}"
CommandParameter="{Binding IsChecked, RelativeSource={RelativeSource Self}}">
</CheckBox>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
ViewModelBase
public ViewModelBase()
{
SelectInterfaceCommand = new SelectedInterfaceCommand(this);
}
And i try to catch my CheckBox IsChecked event inside my SelectedInterfaceCommand Command:
public class SelectedInterfaceCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public ViewModelBase ViewModel { get; set; }
public SelectedInterfaceCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
}
}
And my CanExecute and Execute never called.
I also try to this approach:
Inside my Object class i have this Property:
public bool IsSelected
{
get { return isSelected; }
set
{
isSelected = value;
OnStaticPropertyChanged();
}
}
XAML
<GridViewColumn Header="Select" Width="45">
<GridViewColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox
<CheckBox.IsChecked>
<Binding Path="IsSelected" RelativeSource="{RelativeSource AncestorType={x:Type ListViewItem}}"/>
</CheckBox.IsChecked>
</CheckBox>
</StackPanel>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
And again this IsSelected setter never called.

How to notify viewmodel collection that property on model class has changed

I have a class that has a boolean property called IsChecked.
A collection of this class exist in my viewmodel. I've bound a datagrid in my view to this collection. I need to call a method in my viewmodel when the checkbox in the view gets changed. I've implemented INotifyPropertyChanged on the class and it is firing when I check the box but I don't know how to call the method in my viewmodel.
Here's the class in my model...
public class AccountComponent : INotifyPropertyChanged
{
public string Name { get; set; }
public decimal Amount { get; set; }
private bool _isChecked;
public bool IsChecked
{
get { return _isChecked; }
set
{
_isChecked = value;
NotifyPropertyChanged("IsChecked");
}
}
public bool Enabled { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Here's the collection in my viewmodel...
private ObservableCollection<AccountComponent> _accountComponents;
private string _accountStatus;
public ObservableCollection<AccountComponent> AccountComponents
{
get { return _accountComponents; }
set
{
_accountComponents = value;
NotifyPropertyChanged("AccountComponents");
CalculateComponentTotal();
}
}
Here's my XAML in the view...
<DataGrid ItemsSource="{Binding AccountComponents}" AutoGenerateColumns="False" Margin="5">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<CheckBox IsChecked="{Binding IsChecked, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" IsEnabled="{Binding Enabled}"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Name}" Header="Component" Width="*" IsReadOnly="True" ElementStyle="{DynamicResource TextBlock-Sketch}"/>
<DataGridTextColumn Binding="{Binding Amount,StringFormat={}{0:C}}" IsReadOnly="True" Header="Charge" ElementStyle="{DynamicResource TextBlock-Sketch}">
<DataGridTextColumn.CellStyle>
<Style>
<Setter Property="TextBlock.TextAlignment" Value="Right"/>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Since AccountComponent implements INPC you can observe the IsChecked property in your VM.
say in your VM constructor:
AccountComponents = new ObservableCollection<AccountComponent>();
AccountComponents.CollectionChanged += AccountComponentsOnCollectionChanged;
...
private void AccountComponentsOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) {
if (args.NewItems != null && args.NewItems.Count != 0)
foreach (AccountComponent account in args.NewItems)
account.PropertyChanged += AccountOnPropertyChanged;
if (args.OldItems != null && args.OldItems.Count != 0)
foreach (AccountComponent account in args.OldItems)
account.PropertyChanged -= AccountOnPropertyChanged;
}
private void AccountOnPropertyChanged(object sender, PropertyChangedEventArgs args) {
if (args.PropertyName == "IsChecked")
// Invoke Your VM Function Here
}
That should be it.
In Xaml:
add the following namspace..
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Now for you checkbox add the following code:
<CheckBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding CheckedCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
In ViewModel:
public ICommand CheckedCommand
{
get
{
return new DelegateCommand(OnChecked);//Delegate command is the Implemntation of Icommand Interface
}
}
public void OnLogin(object param)
{
//code for you checked event
}
Hope this will help you.

How to make ListBox editable when bound to a List<string>?

Edit: The basic problem is binding a List to ListBox(or any other control). So I am editing the question.
I bound a list of string to a ListBox as below. However when I change the contents of the textbox it is not changing the string in the source list.Why?
public partial class MainWindow : Window
{
List<string> _nameList = null;
public List<string> NameList
{
get
{
if (_nameList == null)
{
_nameList = new List<string>();
}
return _nameList;
}
set
{
_nameList = value;
}
}
public MainWindow()
{
NameList.Add("test1");
NameList.Add("test2");
InitializeComponent();
}
And the XAML
<ListBox Grid.Row="0" Grid.Column="0" DataContext="{Binding ElementName=main}" ItemsSource="{Binding NameList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding .,Mode=OneWayToSource , UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The DataContext of each ListBoxItem is the string itself, so the path of your binding is empty (.). TwoWay and OneWayToSource bindings require a path, since you can't just replace the current DataContext. So you need to wrap your string in an object that exposes the string as a property:
public class StringItem
{
public string Value { get; set; }
}
Expose the strings as a list of StringItem:
public partial class MainWindow : Window
{
List<StringItem> _nameList = null;
public List<StringItem> NameList
{
get
{
if (_nameList == null)
{
_nameList = new List<StringItem>();
}
return _nameList;
}
set
{
_nameList = value;
}
}
public MainWindow()
{
NameList.Add(new StringItem { Value = "test1" });
NameList.Add(new StringItem { Value = "test2" });
InitializeComponent();
}
And bind to the Value property:
<ListBox Grid.Row="0" Grid.Column="0" DataContext="{Binding ElementName=main}" ItemsSource="{Binding NameList}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note that StringItem will also need to implement INotifyPropertyChanged so that bindings are automatically updated. You should also expose the list as an ObservableCollection<T> rather than a List<T>
May be it helsp?
<ListBox Name="lsbList">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
you can create a DataGridTemplateColumn.CellEditingTemplate with an itemscontrol and textboxes to edit your items
If I didn't misunderstand your question, it is pretty easy to implement. Look:
<ComboBox Text="My Comment 5 with addition." IsEditable="True" Height="25" Width="200">
<ComboBoxItem>My comment1</ComboBoxItem>
<ComboBoxItem>My comment2</ComboBoxItem>
</ComboBox>

Resources