How can I execute an function when mouse left click on a Item in listbox ?
I Can't use SelectChanged because I listen also right click, and when I right click on the item it's execute the fuction SelectChanged also.
Or how to detect in SelectChange method, if event it's right click or left
listBoxG.AddHandler(UIElement.MouseLeftButtonUpEvent, new RoutedEventHandler(OnMouseLeftButtonUp_listBoxG), true);
public void OnMouseLeftButtonUp_listBoxG(Object sender, RoutedEventArgs e)
{
// something
}
WPF:
XAML: install System.Windows.Interactivity.WPF nuget library
<Window x:Class="WpfApp1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:test="clr-namespace:Test"
mc:Ignorable="d"
Title="Window1">
<Window.DataContext>
<test:MainViewModel/>
</Window.DataContext>
<Grid>
<ListBox ItemsSource="{Binding ListBoxItems}" HorizontalAlignment="Left" Width="216" Height="235" Margin="10,10,0,0" VerticalAlignment="Top">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonUp" >
<i:InvokeCommandAction Command="{Binding ListBoxLeftClickCommand}" CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</Window>
ViewModel.cs using the MVVMLight library for RelayCommand and ViewModelBase
using System;
using System.Collections.ObjectModel;
using System.Windows.Input;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
namespace Test
{
public class MainViewModel : ViewModelBase
{
public class TestModel
{
public string Value { get; set; }
public TestModel(string value)
{
this.Value = value;
}
}
public ObservableCollection<string> ListBoxItems { get; set; }
public ICommand ListBoxLeftClickCommand { get; }
public MainViewModel()
{
ListBoxLeftClickCommand = new RelayCommand<object>(DoSomething, selectedItem => true);
ListBoxItems = new ObservableCollection<string>() { "Test1", "Test2" };
}
private void DoSomething(object selectedItem)
{
throw new NotImplementedException();
}
}
}
WinForms:
taken from Right Click to select items in a ListBox
this.ListBox.MouseUp += new System.Windows.Forms.MouseEventHandler(this.List_LeftClick);
private void List_LeftClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
int index = this.listBox.IndexFromPoint(e.Location);
if (index != ListBox.NoMatches)
{
// Do something
}
}
}
Related
I'm a newbie in wpf and i know that this question has been asked other times, and i tried to implement some solutions that i found. But it's not working. I'm doing something wrong but i can't see what it is.
I've created a new simple application to test this problem.
namespace WpfApp3
{
public class MyElement
{
public string Text { get; set; }
public MyElement(string t)
{
Text = t;
}
}
public class MyCommand : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler(parameter);
}
private Action<object> _handler;
public MyCommand(Action<object> handler) { _handler = handler; }
}
public class MyItemsControlViewModel
{
ObservableCollection<MyElement> _items;
public ObservableCollection<MyElement> MyElementItems { get { return _items; } set { _items = value; RaisePropertyChanged("MyElementItems"); } }
ObservableCollection<MyElement> _temporayList;
private ICommand _itemClicked;
public ICommand ItemClicked { get { return _itemClicked; } }
public MyItemsControlViewModel()
{
_items = new ObservableCollection<MyElement>();
_temporayList = new ObservableCollection<MyElement>();
_itemClicked = new MyCommand(OnItemSelected);
AddItem("Element 1");
AddItem("Element 2");
AddItem("Element 3");
UpdateList();
}
public void UpdateList()
{
MyElementItems = _temporayList;
}
public void AddItem(string t)
{
MyElement item = new MyElement(t);
_temporayList.Add(item);
}
public void OnItemSelected(object param)
{
Debug.WriteLine("Executed!");
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
XAML
<UserControl x:Class="WpfApp3.MyUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
d:DesignHeight="1080" d:DesignWidth="570"
x:Name="myCustomControl">
<Grid >
<Button x:Name="btnOutsideItemsControl" Width="100" Height="100 " VerticalAlignment="Top" Command="{Binding ItemClicked}" />
<ItemsControl
x:Name="listItems"
ScrollViewer.PanningMode="None"
IsEnabled="False"
Background = "Transparent"
HorizontalAlignment="Center"
ItemsSource="{Binding MyElementItems}" Margin="0,152,0,0" Width="549">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel HorizontalAlignment="Left" Margin="50,0,0,0"
Background="Transparent" Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Content="{Binding Text}"
Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</UserControl>
The component is used in MainWindow.xaml.
namespace WpfApp3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private MyItemsControlViewModel _myViewModel;
public MyItemsControlViewModel MyViewModel { get { return _myViewModel; } }
public MainWindow()
{
_myViewModel = new MyItemsControlViewModel();
InitializeComponent();
myCustomControl.DataContext = MyViewModel;
}
}
}
XAML
<Window x:Class="WpfApp3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<local:MyUserControl x:Name="myCustomControl"/>
</Grid>
</Window>
When i run the application i can see correctly the list of 3 items with the correct text.
But if i click on one of the button of the list i can't see the output of Debug.WriteLine("Executed!");
But if i click on the button btnOutsideItemsControl that is outside the ItemsControl, it works. I can see the output of Debug.WriteLine("Executed!");
So i think that also the definition of the command is correct.
To bind correctly the Command property of Button inside the ItemsControl i try this
<Button Command="{Binding ElementName=listItems, Path=DataContext.ItemClicked}">
And also this
<Button Command="{Binding DataContext.ItemClicked, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=ItemsControl}}">
But it not works.
Please help!
You're gonna kick yourself once I tell you.
Your problem is that you set IsEnabled="False" on your ItemsControl. Remove it and all will be well with the universe.
I have some problems with my Custom Control - Two way binding don't work when I use it in template.
So I have created template xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApp1"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
>
<ControlTemplate x:Key="YParamCombo" TargetType="ContentControl">
<controls:ParamCombo Header="MY CONTROL TEMPLATE"
Items="{Binding Items}"
PCValue="{Binding Codes[MY_CONTROL_TEMPLATE], Mode=TwoWay}"
Required="True"
MultiSelect="False"/>
</ControlTemplate>
<ControlTemplate x:Key="YComboBox" TargetType="ContentControl">
<ComboBox DisplayMemberPath="Name"
StaysOpenOnEdit="True"
ItemsSource="{Binding Items}"
SelectedValue="{Binding Codes[STANDARD_TEMPLATE], Mode=TwoWay}"
SelectedValuePath="Code"/>
</ControlTemplate>
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
xmlns:controls="clr-namespace:GUIControls;assembly=GUIControls"
Title="MainWindow" Height="250" Width="525">
<Grid Margin="0,0,0,-1">
<Button Margin="62,162,299,4" Content="Show Codes-1" Click="Button_Click2"></Button>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<HeaderedContentControl Header="STANDARD CONTROL XAML" >
<ComboBox DisplayMemberPath="Name"
StaysOpenOnEdit="True"
ItemsSource="{Binding Items}"
SelectedValue="{Binding Codes[STANDARD_XAML]}"
SelectedValuePath="Code"/>
</HeaderedContentControl>
<HeaderedContentControl Header="STANDARD CONTROL TEMPLATE" >
<ContentControl Height="23" Template="{StaticResource YComboBox}"/>
</HeaderedContentControl>
<ContentControl Height="44" Template="{StaticResource YParamCombo}">
</ContentControl>
<controls:ParamCombo Header="MY CONTROL XAML"
Items="{Binding Items}"
PCValue="{Binding Codes[MYCONTROL_XAML], Mode=TwoWay}"
Required="True"
MultiSelect="False"/>
</StackPanel>
</Grid>
cs
using System.Linq;
using System.Windows;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DataContext = new WModel();
InitializeComponent();
}
private WModel vm { get { return (DataContext as WModel); } }
private void Button_Click2(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Join(";", vm.Codes._codes.Select(x => x.Key + "=" + x.Value).ToArray()));
}
}
}
using GUIControls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace WpfApp1
{
public class WModel
{
public WModel()
{
Codes = new CodesClass();
}
public string Caption { get; set; }
public ObservableCollection<Dict> Items
{
get
{
return new ObservableCollection<Dict>()
{
new Dict(){ Name = "Name1", Code = "Code1" } ,
new Dict(){ Name = "Name2", Code = "Code2" }
};
}
}
public CodesClass Codes { get; set; }
}
public class Dict : IDict
{
public string Name { get; set; }
public string Code { get; set; }
}
public class CodesClass
{
public Dictionary<string, object> _codes;
public CodesClass()
{
_codes = new Dictionary<string, object>();
}
public object this[string param]
{
get
{
if (_codes.ContainsKey(param))
return _codes[param];
else
return null;// "I have no " + param;
}
set
{
_codes[param] = value;
}
}
}
}
When I run app and select all 4 comboboxes and Press button, I can see that twoway binding in one combobox(Custom Control declared in template ) do not work
---------------------------
---------------------------
STANDARD_XAML=Code2;STANDARD_TEMPLATE=Code2;MYCONTROL_XAML=Code2
---------------------------
ОК
---------------------------
Some code from control
public static readonly DependencyProperty PCValueProperty =
DependencyProperty.Register("PCValue", typeof(string), typeof(ParamCombo),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, new PropertyChangedCallback(OnPCValuePropertyChanged)));
//new PropertyMetadata(new PropertyChangedCallback(OnValuePropertyChanged)));, new PropertyChangedCallback(OnPCValuePropertyChanged))
#endregion
private static void OnPCValuePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ParamCombo paramCombo = (ParamCombo)sender;
paramCombo.UpdateSelected((e.NewValue == null) ? "" : e.NewValue.ToString());
}
Thanks for your help!
I have had problems with twoway binding in a combo in a customcontrol template and the solution was to override OnApplyTemplate in the custom control, using Template.FindName to get the combo, get the DataContex object of the combo and raise PropertyChanged in the DataContext object for the bound property. My problem was to update the combo when the window was loaded.
I am trying to find out how to process drag and dropped Dropped files in my listbox in a usercontrol and bind them to update an ObservableCollection in my ViewModel.
Here's my listBox in XAML:
<ListBox x:Name="listDrop" Height="50" Margin="0,0,0,0" AllowDrop="True" Drop="dropfiles" >
</ListBox>
Now the codebehind:
public partial class ProcessXML : UserControl
{
public ProcessXML()
{
InitializeComponent();
}
private void dropfiles(object sender, DragEventArgs e)
{
string[] droppedFiles = null;
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
droppedFiles = e.Data.GetData(DataFormats.FileDrop, true) as string[];
}
if ((null == droppedFiles) || (!droppedFiles.Any())) { return; }
listDrop.Items.Clear();
foreach (string s in droppedFiles)
{
listDrop.Items.Add(s);
}
}
}
This lists the path to the files being dropped, working fine, but how do I send that info or use this to process it?
I would like to send this data to my Viewmodel, ideally to an ObservableCollection and then process each items but I've been scratching my head and can't find a way. How would I accomplish that?
There's nothing in your code to indicate that you even have a viewmodel, or what you're using it for. I'm going to assume -- wrongly, I suspect -- that it's the DataContext for the usercontrol.
If you want your viewmodel to do something when files are dropped, you can have it handle the CollectionChanged event of its DropFiles collection, or you can give it a method for the file-drop handler to call.
private void dropfiles(object sender, DragEventArgs e)
{
string[] droppedFiles = null;
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
droppedFiles = e.Data.GetData(DataFormats.FileDrop, true) as string[];
}
if ((null == droppedFiles) || (!droppedFiles.Any())) { return; }
var myVM = DataContext as MyViewModel;
// MyViewModel.DropFiles should be ObservableCollection<String>
myVM.DropFiles.Clear();
foreach (string s in droppedFiles)
{
myVM.DropFiles.Add(s);
}
}
Alternate:
private void dropfiles(object sender, DragEventArgs e)
{
string[] droppedFiles = null;
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
droppedFiles = e.Data.GetData(DataFormats.FileDrop, true) as string[];
}
if ((null == droppedFiles) || (!droppedFiles.Any())) { return; }
var myVM = DataContext as MyViewModel;
myVM.DoFileDrop(droppedFiles);
}
MyViewModel.cs
public void DoFileDrop(IEnumerable<String> filePaths)
{
DropFiles.Clear();
foreach (var s in filePaths)
DropFiles.Add(s);
// Do other stuff if you want to
}
XAML
<ListBox
x:Name="listDrop"
Height="50"
Margin="0,0,0,0"
AllowDrop="True"
Drop="dropfiles"
ItemsSource="{Binding DropFiles}"
/>
If you are binding ViewModel to DataContext of ProcessXML then you can access your ViewModel like this:
private void dropfiles(object sender, DragEventArgs e)
{
// some code goes here ...
var vm = this.DataContext as ViewModelType;
if (vm == null) return;
// Do your stuff with vm instance below
}
EDITED
I think you want something like this:
<Window x:Name="root">
<Grid x:Name="someParentControl">
<controls:ProcessXML ParentViewModel={Binding Path=DataContext, ElementName=root}/>
</Grid>
</Window>
And ProcessXML code behind
public partial class ProcessXML : UserControl
{
public ProcessXML()
{
InitializeComponent();
}
DependencyProperty ParentViewModelProperty = DependencyProperty
.Register("ParentViewModel", typeof(ViewModelType), typeof(ProcessXML), new PropertyMetadata(null));
public ViewModelType ParentViewModel
{
get { return (ViewModelType)GetValue(ParentViewModelProperty); }
set { SetValue(ParentViewModelProperty, value); }
}
}
Here is how to achieve that using MvvmLightLibs:
ObservableCollection<T>
EventToCommand
Window XAML:
<Window
x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:wpfApp1="clr-namespace:WpfApp1"
d:DataContext="{d:DesignInstance wpfApp1:Model}"
mc:Ignorable="d">
<Window.DataContext>
<wpfApp1:Model />
</Window.DataContext>
<Grid>
<wpfApp1:UserControl1 />
</Grid>
</Window>
Window code:
namespace WpfApp1
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
}
}
User control XAML:
<UserControl
x:Class="WpfApp1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:command="http://www.galasoft.ch/mvvmlight"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApp1"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance local:Model}"
mc:Ignorable="d">
<Grid>
<ListBox AllowDrop="True" ItemsSource="{Binding Files}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Drop">
<command:EventToCommand Command="{Binding Load}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
</Grid>
</UserControl>
User control code:
namespace WpfApp1
{
public partial class UserControl1
{
public UserControl1()
{
InitializeComponent();
}
}
}
Model:
using System.Collections.ObjectModel;
using System.Windows;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.CommandWpf;
namespace WpfApp1
{
public class Model : ViewModelBase
{
public Model()
{
Files = new ObservableCollection<string>();
Load = new RelayCommand<DragEventArgs>(e =>
{
var files = e.Data.GetData(DataFormats.FileDrop) as string[];
if (files == null)
return;
foreach (var file in files)
{
Files.Add(file);
}
});
}
public RelayCommand<DragEventArgs> Load { get; }
public ObservableCollection<string> Files { get; }
}
}
This is a simple example, obviously you will want to further adjust it to your needs.
I have a main control (MainWindow.xaml) and an user control (ItemView.xaml). MainWindow contains an ItemsControl for all the ItemView-s and a simple button to add an item. All logic is (should be?) inside two corresponding viewmodels (MainWindowViewModel and ItemViewModel). Below is my code (made it as short as possible), but I have two problems with it:
When a new item is added it is correctly displayed but the exception is raised (Cannot create default converter to perform 'two-way' conversions between types 'WpfApplication1.ItemView' and 'WpfApplication1.ItemViewModel'.).
The OnDelete event handler in MainWindowViewModel is never raised? Edit: actually the ViewModel property inside BtnDeleteClick is null so yeah... of course.
Btw - I use Fody PropertyChanged.
MainWindow.xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Button Grid.Row="0" Width="100" Height="35" Content="Add" HorizontalAlignment="Left" Margin="10" Click="BtnAddClick"></Button>
<Border Grid.Row="1" MinHeight="50">
<ItemsControl ItemsSource="{Binding ViewModel.Items}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<wpfApplication1:ItemView ViewModel="{Binding ., PresentationTraceSources.TraceLevel=High, Mode=TwoWay}"></wpfApplication1:ItemView>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
</Grid>
</Window>
MainWindow.xaml.cs:
[ImplementPropertyChanged]
public partial class MainWindow
{
public MainWindowViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainWindowViewModel();
}
private void BtnAddClick(object sender, RoutedEventArgs e)
{
ViewModel.Add();
}
}
MainWindowViewModel.cs:
[ImplementPropertyChanged]
public class MainWindowViewModel
{
public ObservableCollection<ItemViewModel> Items { get; set; }
public MainWindowViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public void Add()
{
var item = new ItemViewModel();
item.OnDelete += (sender, args) =>
{
Debug.WriteLine("-- WAITING FOR THIS TO HAPPEN --");
Items.Remove(item);
};
Items.Add(item);
}
}
ItemViewModel.xaml:
<UserControl x:Class="WpfApplication1.ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Button Width="100" Height="35" Content="Delete" Click="BtnDeleteClick"></Button>
</Grid>
</UserControl>
ItemView.xaml.cs:
[ImplementPropertyChanged]
public partial class ItemView
{
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register
(
"ViewModel", typeof(ItemViewModel), typeof(ItemView), new UIPropertyMetadata(null)
);
public ItemViewModel ViewModel
{
get { return (ItemViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public ItemView()
{
InitializeComponent();
}
private void BtnDeleteClick(object sender, RoutedEventArgs e)
{
ViewModel.Delete();
}
}
And ItemViewModel.cs:
[ImplementPropertyChanged]
public class ItemViewModel
{
public event EventHandler OnDelete;
public void Delete()
{
var handler = OnDelete;
if (handler != null)
{
handler(this, new EventArgs());
}
}
}
You should not set
DataContext="{Binding RelativeSource={RelativeSource Self}}"
in the XAML of your ItemView. It effectively breaks the ViewModel="{Binding .}" binding in MainWindow.xaml, because the DataContext is no longer an ItemsViewModel, but an ItemsView.
As a rule, you should never explicitly set the DataContext of a UserControl, because all "external" bindings would then require an explicit Source or RelativeSource value.
That said, you're doing all this way too complicated. Instead of having a button click handler in your ItemsView, you could simply have a view model with a delete command, and bind the Button's Command property to this command.
It may look like this:
public class ItemViewModel
{
public string Name { get; set; }
public ICommand Delete { get; set; }
}
public class MainViewModel
{
public MainViewModel()
{
Items = new ObservableCollection<ItemViewModel>();
}
public ObservableCollection<ItemViewModel> Items { get; private set; }
public void AddItem(string name)
{
Items.Add(new ItemViewModel
{
Name = name,
Delete = new DelegateCommand(p => Items.Remove(p as ItemViewModel))
});
}
}
and would be used like this:
<UserControl x:Class="WpfApplication1.ItemView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Button Content="Delete"
Command="{Binding Delete}"
CommandParameter="{Binding}"/>
</Grid>
</UserControl>
I have a global input binding for the key period (.). I'd still like to be able to enter it in a TextBox? Is there a way to accomplish this?
Here's a simple sample case. The command is executed when I type the period in the TextBox.
XAML:
<Window x:Class="UnrelatedTests.Case6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Window.InputBindings>
<KeyBinding Key="OemPeriod" Command="{Binding Command}" />
</Window.InputBindings>
<Grid>
<TextBox >Unable to type "." here!</TextBox>
</Grid>
</Window>
C#:
using System;
using System.Windows;
using System.Windows.Input;
namespace UnrelatedTests.Case6
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = this;
}
public ICommand Command
{
get { return new CommandImpl(); }
}
private class CommandImpl : ICommand
{
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
MessageBox.Show("executed!");
}
public event EventHandler CanExecuteChanged;
}
}
}
You can bind the Key in your KeyBinding and change it's value to Key.None when your TextBox got focus:
Xaml:
<Window x:Class="UnrelatedTests.Case6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300"
GotFocus="Window_GotFocus">
<Window.InputBindings>
<KeyBinding Key="{Binding MyKey}" Command="{Binding Command}" />
</Window.InputBindings>
<Grid>
<TextBox/>
</Grid>
</Window>
MainWindow.cs: (with INotifyPropertyChanged implemented)
Key _myKey;
public Key MyKey
{
get
{
return _myKey;
}
set
{
_myKey = value;
OnPropertyChanged("MyKey");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
MyKey = Key.OemPeriod;
}
private void Window_GotFocus(object sender, RoutedEventArgs e)
{
if (e.OriginalSource is TextBox)
MyKey = Key.None;
else
MyKey = Key.OemPeriod;
}