I have been trying to figure this out with lots of googling and SO, but unfortunately I cannot solve this issue. The more I read, the more confused I get.
I would like to build an autocomplete textbox as a custom control.
My CustomControl:
<UserControl x:Class="ApplicationStyling.Controls.AutoCompleteTextBox"
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:ApplicationStyling.Controls"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Name="AutoCompleteBox">
<Grid>
<TextBox Grid.Row="3"
Style="{DynamicResource InputBox}"
x:Name="SearchBox"
Text="{Binding Text}"
TextChanged="{Binding ElementName=AutoCompleteBox, Path=TextChanged}"/>
<ListBox x:Name="SuggestionList"
Visibility="Collapsed"
ItemsSource="{Binding ElementName=AutoCompleteTextBox, Path=SuggestionsSource}"
SelectionChanged="{Binding ElementName=AutoCompleteBox, Path=SelectionChanged}">
<ListBox.ItemTemplate>
<DataTemplate>
<Label Content="{Binding Label}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</UserControl>
My Code Behind:
using System.Collections;
using System.Windows;
using System.Windows.Controls;
namespace ApplicationStyling.Controls
{
/// <summary>
/// Interaction logic for AutoCompleteTextBox.xaml
/// </summary>
public partial class AutoCompleteTextBox : UserControl
{
public static readonly DependencyProperty SuggestionsSourceProperty;
public static readonly DependencyProperty TextProperty;
// Events
public static readonly RoutedEvent TextChangedProperty;
public static readonly RoutedEvent SelectionChangedProperty;
static AutoCompleteTextBox()
{
// Attributes
AutoCompleteTextBox.SuggestionsSourceProperty = DependencyProperty.Register("SuggestionsSource", typeof(IEnumerable), typeof(AutoCompleteTextBox));
AutoCompleteTextBox.TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteTextBox));
// Events
AutoCompleteTextBox.TextChangedProperty = EventManager.RegisterRoutedEvent("TextChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AutoCompleteTextBox));
AutoCompleteTextBox.SelectionChangedProperty = EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AutoCompleteTextBox));
}
#region Events
public event RoutedEventHandler TextChanged
{
add { AddHandler(TextChangedProperty, value); }
remove { RemoveHandler(TextChangedProperty, value); }
}
// This method raises the Tap event
void RaiseTextChangedEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(AutoCompleteTextBox.TextChangedProperty);
RaiseEvent(newEventArgs);
}
public event RoutedEventHandler SelectionChanged
{
add { AddHandler(SelectionChangedProperty, value); }
remove { RemoveHandler(SelectionChangedProperty, value); }
}
// This method raises the Tap event
void RaiseSelectionChangedEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(AutoCompleteTextBox.SelectionChangedProperty);
RaiseEvent(newEventArgs);
}
#endregion
#region DProperties
/// <summary>
/// IEnumerable ItemsSource Property for the Suggenstion Box
/// </summary>
public IEnumerable SuggestionsSource
{
get
{
return (IEnumerable)GetValue(AutoCompleteTextBox.SuggestionsSourceProperty);
}
set
{
SetValue(AutoCompleteTextBox.SuggestionsSourceProperty, value);
}
}
/// <summary>
/// This is the Text attribute which routes to the Textbox
/// </summary>
public string Text
{
get
{
return (string)GetValue(AutoCompleteTextBox.TextProperty);
}
set
{
SetValue(AutoCompleteTextBox.TextProperty, value);
}
}
#endregion
public AutoCompleteTextBox()
{
InitializeComponent();
SearchBox.TextChanged += (sender, args) => RaiseTextChangedEvent();
SuggestionList.SelectionChanged += (sender, args) => RaiseSelectionChangedEvent();
}
}
}
And lastly, the way I use it:
<asc:AutoCompleteTextBox x:Name="ShareAutoCompleteBox"
Grid.Row="3"
SelectionChanged="ShareAutoCompleteBox_SelectionChanged"
TextChanged="ShareAutoCompleteBox_TextChanged"/>
where asc is the namespace for the outsourced class library which is loaded via app.xaml.
Anyways, the issues I am getting in the XAML at the TextBox.TextChanged attribute, and when running the code:
System.InvalidCastException: Unable to cast object of type 'System.Reflection.RuntimeEventInfo' to type 'System.Reflection.MethodInfo'.
So what exactly is going on here? I would like to forward the AutoCompleteTextBox TextChanged to the TextBox within the Custom Control Template. Same with the SelectionChanged to the Listbox.
I took most of the code from either https://msdn.microsoft.com/en-us/library/ms752288(v=vs.100).aspx (for the events) and from some other SO questions the code for the custom properties.
Not sure, what the problem is and I am looking forward to your help.
The exception is happening because you are trying to bind the value of the TextChanged field to an attribute that expects a method reference. It's really confusing to WPF. :)
Just remove the TextChanged attribute from the TextBox element in your XAML:
TextChanged="{Binding ElementName=AutoCompleteBox, Path=TextChanged}"
You already subscribe to the event in your constructor, which is enough. If you do want to use the TextChanged attribute instead of subscribing in the constructor, then you can do that, but you need to provide an actual event handler, e.g. a method in the code-behind. That method would just call the RaiseTextChangedEvent() method, just as your current event handler does. It's just that it would be a named method in the class instead of an anonymous method declared in the constructor.
Same thing applies to the other event.
That said, you might reconsider implementing the forwarded events at all. Typically, your control's Text property would be bound to the property of some model object, which can itself react appropriately when that bound property changes. It shouldn't need a separate event on the UserControl object to tell it that its value has changed.
Related
I have a very simple solution below for tabs being populated based on MVVM. How do I setup the following two commands, and 'Add' and 'Remove'. From what I've read online it appears I need to setup ICommand or something along those lines. It wasn't clear enough to me in the demos for me to get it working.
The Add command would call the already existing function in the ViewModel class. Bu it would be called by a key command 'Ctrl + N'
The Remove command would be called when the user clicks the 'X' button, which would remove that particular tab. Otherwise it can be called by 'Ctrl + W' which would close whichever tab is currently selected.
The command stuff is new to me, so if someone could help me out it would be greatly appreciated. I hope to expand upon this and continue adding more to the tool.
Link to visual studio dropbox files. You'll see I've broken things out to classes and organized it in a way that makes things clear.
Snippets of tool below...
View Model
using System;
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication1
{
public class ViewModel : ObservableObject
{
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
public void AddContentItem()
{
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
}
public class TabItem
{
public string Header { get; set; }
public string Content { get; set; }
}
}
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:data="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="250">
<Window.DataContext>
<data:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--<Button Content="Add" Command="{Binding AddCommand}" Grid.Row="0"></Button>-->
<TabControl ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
<Button Content="x" Width="20" Height="20" Margin="5 0 0 0"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
You've received two other answers already. Unfortunately, neither precisely addresses both the Add and Remove commands. Also, one prefers to focus primarily on code-behind implementation rather than XAML declarations, and is fairly sparse on details anyway, while the other more-correctly focuses on implementation in XAML where appropriate, but does not include correct, working code, and (slightly) obfuscates the answer by introducing the extra abstraction of the RelayCommand type.
So, I will offer my own take on the question, with the hopes this will be more useful to you.
While I agree that abstracting the ICommand implementation into a helper class such as RelayCommand is useful and even desirable, unfortunately this tends to hide the basic mechanisms of what's going on, and requires a more elaborate implementation that was offered in the other answer. So for now, let's ignore that.
Instead, just focus on what does need to be implemented: two different implementations of the ICommand interface. Your view model will expose these as the values of two bindable properties representing the commands to be executed.
Here is a new version of your ViewModel class (with the irrelevant and unprovided ObservableObject type removed):
class ViewModel
{
private class AddCommandObject : ICommand
{
private readonly ViewModel _target;
public AddCommandObject(ViewModel target)
{
_target = target;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_target.AddContentItem();
}
}
private class RemoveCommandObject : ICommand
{
private readonly ViewModel _target;
public RemoveCommandObject(ViewModel target)
{
_target = target;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_target.RemoveContentItem((TabItem)parameter);
}
}
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ICommand AddCommand { get { return _addCommand; } }
public ICommand RemoveCommand { get { return _removeCommand; } }
private readonly ICommand _addCommand;
private readonly ICommand _removeCommand;
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
_addCommand = new AddCommandObject(this);
_removeCommand = new RemoveCommandObject(this);
}
public void AddContentItem()
{
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
public void RemoveContentItem(TabItem item)
{
TabItems.Remove(item);
}
}
Note the two added nested classes, AddCommandObject and RemoveCommandObject. These are both examples of nearly the simplest implementation of ICommand possible. They can always be executed, and so the return value of CanExecute() never changes (so there's no need to ever raise the CanExecuteChanged event). They do need the reference to your ViewModel object so that they can each call the appropriate method.
There are also two public properties added to allow binding of these commands. Of course, the RemoveContentItem() method needs to know what item to remove. This needs to be set up in the XAML, so that the value can be passed as a parameter to the command handler and from there to the actual RemoveContentItem() method.
In order to support the use of the keyboard for the commands, one approach is to add input bindings to the window. This is what I've chosen here. The RemoveCommand binding additionally needs the item to be deleted to be passed as the command parameter, so this is bound to the CommandParameter for the KeyBinding object (just as for the CommandParameter of the Button in the item).
The resulting XAML looks like this:
<Window.DataContext>
<data:ViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Command="{Binding AddCommand}">
<KeyBinding.Gesture>
<KeyGesture>Ctrl+N</KeyGesture>
</KeyBinding.Gesture>
</KeyBinding>
<KeyBinding Command="{Binding RemoveCommand}"
CommandParameter="{Binding SelectedItem, ElementName=tabControl1}">
<KeyBinding.Gesture>
<KeyGesture>Ctrl+W</KeyGesture>
</KeyBinding.Gesture>
</KeyBinding>
</Window.InputBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TabControl x:Name="tabControl1" ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
<Button Content="x" Width="20" Height="20" Margin="5 0 0 0"
Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType=TabControl}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource Self}}">
</Button>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
EDIT:
As I mentioned above, there is in fact benefit to abstracting the ICommand implementation, using a helper class instead of declaring a new class for every command you want to implement. The referenced answer at Why RelayCommand mentions loose coupling and unit testing as motivations. While I agree these are good goals, I can't say that these goals are in fact served per se by the abstraction of the ICommand implementation.
Rather, I see the benefits as being the same ones primarily found when making such abstractions: it allows for code reuse, and in doing so improves developer productivity, along with code maintainability and quality.
In my above example, every time you want a new command, you have to write a new class that implements ICommand. On the one hand, this means that each class you write can be tailor-made to the specific purpose. Dealing with CanExecuteChanged or not, as the case requires, passing parameters or not, etc.
On the other hand, every time you write such a class, that's an opportunity to write a new bug. Worse, if you introduce a bug which is then later copy/pasted, then when you eventually find the bug, you may or may not fix it everywhere it exists.
And of course, writing such classes over and over gets tedious and time-consuming.
Again, these are just specific examples of the general conventional wisdom of the "best practice" of abstracting reusable logic.
So, if we've accepted that an abstraction is useful here (I certainly have :) ), then the question becomes, what does that abstraction look like? There are a number of different ways to approach the question. The referenced answer is one example. Here is a slightly different approach that I've written:
class DelegateCommand<T> : ICommand
{
private readonly Func<T, bool> _canExecuteHandler;
private readonly Action<T> _executeHandler;
public DelegateCommand(Action<T> executeHandler)
: this(executeHandler, null) { }
public DelegateCommand(Action<T> executeHandler, Func<T, bool> canExecuteHandler)
{
_canExecuteHandler = canExecuteHandler;
_executeHandler = executeHandler;
}
public bool CanExecute(object parameter)
{
return _canExecuteHandler != null ? _canExecuteHandler((T)parameter) : true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeHandler((T)parameter);
}
public void RaiseCanExecuteChanged()
{
EventHandler handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
In the ViewModel class, the above would be used like this:
class ViewModel
{
private ObservableCollection<TabItem> tabItems;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ICommand AddCommand { get { return _addCommand; } }
public ICommand RemoveCommand { get { return _removeCommand; } }
private readonly ICommand _addCommand;
private readonly ICommand _removeCommand;
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
// Use a lambda delegate to map the required Action<T> delegate
// to the parameterless method call for AddContentItem()
_addCommand = new DelegateCommand<object>(o => this.AddContentItem());
// In this case, the target method takes a parameter, so we can just
// use the method directly.
_removeCommand = new DelegateCommand<TabItem>(RemoveContentItem);
}
Notes:
Of course, now the specific ICommand implementations are no longer needed. The AddCommandObject and RemoveCommandObject classes have been removed from the ViewModel class.
In their place, the code uses the DelegateCommand<T> class.
Note that in some cases, the command handler is not going to need the parameter passed to the ICommand.Execute(object) method. In the above, this is addressed by accepting the parameter in a lambda (anonymous) delegate, and then ignoring it while calling the parameterless handler method. Other ways to approach this would be to have the handler method accept the parameter but then ignore it, or to have a non-generic class in which the handler delegate itself can be parameterless. IMHO, there's no "right way" per seā¦just various choices that may be considered more or less preferable according to personal preference.
Note also that this implementation differs from the referenced answer's implementation in the handling of the CanExecuteChanged event. In my implementation, the client code is given fine-grained control over that event, at the expense of requiring the client code to retain a reference to the DelegateCommand<T> object in question and calling its RaiseCanExecuteChanged() method at the appropriate time. In the other implementation, it instead relies on the CommandManager.RequerySuggested event. This is a trade-off of convenience vs efficiency and, in some cases, correctness. That is, it is less convenient for the client code to have to retain references to commands which may change the executable status, but if one goes the other route, at the very least the CanExecuteChanged event may be raised much more often than is required, and in some cases it's even possible it may not be raised when it should have been (which is far worse than the possible inefficiency).
On that last point, yet another approach would be to make the ICommand implementation a dependency object, and provide a dependency property that is used to control the executable state of the command. This is a lot more complicated, but overall could be considered the superior solution as it allows fine-grained control over the CanExecuteChanged event's raising, while providing a good, idiomatic way to bind the executable state of the command, e.g. in XAML to whatever property or properties actually determine said executability.
Such an implementation might look something like this:
class DelegateDependencyCommand<T> : DependencyObject, ICommand
{
public static readonly DependencyProperty IsExecutableProperty = DependencyProperty.Register(
"IsExecutable", typeof(bool), typeof(DelegateCommand<T>), new PropertyMetadata(true, OnIsExecutableChanged));
public bool IsExecutable
{
get { return (bool)GetValue(IsExecutableProperty); }
set { SetValue(IsExecutableProperty, value); }
}
private static void OnIsExecutableChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DelegateDependencyCommand<T> command = (DelegateDependencyCommand<T>)d;
EventHandler handler = command.CanExecuteChanged;
if (handler != null)
{
handler(command, EventArgs.Empty);
}
}
private readonly Action<T> _executeHandler;
public DelegateDependencyCommand(Action<T> executeHandler)
{
_executeHandler = executeHandler;
}
public bool CanExecute(object parameter)
{
return IsExecutable;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_executeHandler((T)parameter);
}
}
In the above, the canExecuteHandler argument for the class is eliminated, in lieu of the IsExecutable property. When that property changes, the CanExecuteChanged event is raised. IMHO it's unfortunate that there is this discrepancy in the ICommand interface between how it's designed and how WPF normally works (i.e. with bindable properties). It's a bit weird that we have essentially a property but which is exposed via an explicit getter method named CanExecute().
On the other hand, this discrepancy does serve some useful purposes, including making explicit and convenient the use of the CommandParameter for both the execution of the command, and the checking for executability. These are worthwhile goals. I'm just not sure personally whether I'd have made the same choice balancing them with the consistency of the usual way state is connected within WPF (i.e. through binding). Fortunately, it's simple enough to implement the ICommand interface in a bindable way (i.e. as above), if that's really desired.
You can start with remove.
First, you will need to create a RelayCommand class. More info about RelayCommand, see this post: Why RelayCommand
public class RelayCommand : ICommand
{
#region Private members
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
private readonly Action execute;
/// <summary>
/// True if command is executing, false otherwise
/// </summary>
private readonly Func<bool> canExecute;
#endregion
/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/> that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action execute): this(execute, canExecute: null)
{
}
/// <summary>
/// Initializes a new instance of <see cref="RelayCommand"/>.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
this.execute = execute;
this.canExecute = canExecute;
}
/// <summary>
/// Raised when RaiseCanExecuteChanged is called.
/// </summary>
public event EventHandler CanExecuteChanged;
/// <summary>
/// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
/// <returns>True if this command can be executed; otherwise, false.</returns>
public bool CanExecute(object parameter)
{
return this.canExecute == null ? true : this.canExecute();
}
/// <summary>
/// Executes the <see cref="RelayCommand"/> on the current command target.
/// </summary>
/// <param name="parameter">
/// Data used by the command. If the command does not require data to be passed, this object can be set to null.
/// </param>
public void Execute(object parameter)
{
this.execute();
}
/// <summary>
/// Method used to raise the <see cref="CanExecuteChanged"/> event
/// to indicate that the return value of the <see cref="CanExecute"/>
/// method has changed.
/// </summary>
public void RaiseCanExecuteChanged()
{
var handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
Next, add a "bindable" Remove property in your ViewModel of type RelayCommand. This is what your TabItem buttons will bind to, and how it will notify the ViewModel it was pressed. The ReplayCommand will have an execute method that will get called whenever a press occurs. Here, we remove our-self from the overall TabItem list.
public class ViewModel : ObservableObject
{
private ObservableCollection<TabItem> tabItems;
private RelayCommand<object> RemoveCommand;
public ObservableCollection<TabItem> TabItems
{
get { return tabItems ?? (tabItems = new ObservableCollection<TabItem>()); }
}
public ViewModel()
{
TabItems.Add(new TabItem { Header = "One", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Two", Content = DateTime.Now.ToLongDateString() });
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
RemoveCommand = new RelayCommand<object>(RemoveItemExecute);
}
public void AddContentItem()
{
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString() });
}
private void RemoveItemExecute(object param)
{
var tabItem = param as TabItem;
if (tabItem != null)
{
TabItems.Remove(tabItem);
}
}
}
Now, update your XAML. Each TabItem will need to bind to the RemoveCommand in the parent ViewModel and pass itself in as a parameter. We can do this like:
<!--<Button Content="Add" Command="{Binding AddCommand}" Grid.Row="0"></Button>-->
<TabControl x:Name="TabItems" ItemsSource="{Binding TabItems}" Grid.Row="1" Background="LightBlue">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
<TextBlock Text="{Binding Header}" VerticalAlignment="Center"/>
<Button Command="{Binding ElementName=TabItems, Path=DataContext.RemoveCommand}"
CommandParameter="{Binding Path=DataContext, RelativeSource={RelativeSource Self}}"
Content="x"
Width="20"
Height="20"
Margin="5 0 0 0"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
At first u need to setup your commands
public static class Commands
{
private static RoutedUICommand add;
private static RoutedUICommand remove;
static Commands()
{
searchValue = new RoutedUICommand("Add", "Add", typeof(Commands));
showCSCode = new RoutedUICommand("Remove", "Remove", typeof(Commands));
add.InputGestures.Add(new KeyGesture(Key.N, ModifierKeys.Control));
remove.InputGestures.Add(new KeyGesture(Key.X));
}
public static RoutedUICommand Add { get { return add; } }
public static RoutedUICommand Remove { get { return remove; } }
}
In window loaded event you have to bind the command methods
<window ... Loaded="window_loaded">
The cs file
CommandBindings.Add(new CommandBinding(Commands.Remove, HandleRemoveExecuted, HandleCanRemoveExecuted));
Is command enabled:
private void HandleCanAddExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
What should the command do:
private void HandleAddExecute(object sender, ExecutedRoutedEventArgs e)
{
AddContentItem();
}
At last you just have to edit your existing files with
TabItems.Add(new TabItem { Header = "Three", Content = DateTime.Now.ToLongDateString(),
CommandBindings.Add(new CommandBinding(Commands.Add, HandleAddExecuted, HandleCanAddExecuted)); });
xaml:
<Window ...
xmlns:commands="clr-namespace:<NAMESPACE>">
<Button Content="x" Width="20"
Height="20" Margin="5 0 0 0"
Command="{x:Static commands:Commands.Remove}"/>
I have an issue whereby I am not receiving updates through my bindings.
I have a label which is bound to the ExtentWidth of the TextBox property via the DataContext.
My binding initially works and displays the value of 0 in the label however it does not update after this.
ExtentWidth is a read only property, I'm not sure if this affects the binding in any way but I have a label the binds to the text when it is set so I know it can receive updates. (button updates text and label is updated)
below is some code to demonstrate my issue.
Xaml
<Window x:Class="TestHarnesses.Views.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">
<Grid>
<StackPanel>
<ContentPresenter x:Name="ContentPresenter" Content="{Binding}"></ContentPresenter>
<Label x:Name="lblExtentWidth"
Content="{Binding ExtentWidth, Mode=OneWay}"/>
<Label x:Name="lblText"
Content="{Binding Text, Mode=OneWay}"/>
<Button Content="Different Jibber Jabber" Click="ButtonBase_OnClick"/>
</StackPanel>
</Grid>
</Window>
Code behind
using System.Windows;
using System.Windows.Controls;
namespace TestHarnesses.Views
{
///
<summary>
/// Interaction logic for Window1.xaml
///</summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
TextBox tb = new TextBox(){Text = "Jibber Jabber"};
this.TestTextBox = tb;
}
public TextBox TestTextBox
{
get { return (TextBox)GetValue(TestTextBoxProperty); }
set { SetValue(TestTextBoxProperty, value); }
}
// Using a DependencyProperty as the backing store for TestTextBox. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TestTextBoxProperty =
DependencyProperty.Register("TestTextBox", typeof(TextBox), typeof(Window1), new PropertyMetadata(OnTestTextBoxProperty));
private static void OnTestTextBoxProperty(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((Window1) d).DataContext = (TextBox) e.NewValue;
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
TestTextBox.Text = "Different Jibber Jabber";
}
}
}
Your View is not being notified about changes to the ExtentWidth property because ExtentWidth is not a DependencyProperty, nor does the TextBox class implement INotifyPropertyChanged. Also, there does not appear to be a corresponding Changed event associated with this property.
If you want to update your view automatically with the most recent ExtentWidth then you'll need to listen to a different property/event (perhaps the SizeChanged event?) which gets updated simultaneously.
In my View, I try to tie an event to the Enter key by the following XAML:
<TextBox x:Name="txtFields" Text="{Binding FieldsTextProperty, UpdateSourceTrigger=PropertyChanged}" Height="23" TextWrapping="NoWrap" Background="#FFCBEECD" AcceptsReturn="False" >
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding AddFieldCommand}"></KeyBinding>
</TextBox.InputBindings>
</TextBox>
The AddFieldCommand exists in my ViewModel as a property:
public ICommand AddFieldCommand { get; private set; }
In the ViewModel Constructor the following RelayCommand exists.
AddFieldCommand = new RelayCommand(AddField);
And from the RelayCommand the method AddField is called.
public void AddField()
{
Console.WriteLine("AddField Method")
}
This doesn't work - the AddField method is never called. Can anybody help?
I wonder if the the .InputBindings aren't working in this scenario. Keyboard input handling is probably being hijacked by the TextBox.
Assuming you want to stick to the MVVM pattern and avoid event-handling code in the code-behind, I'd probably choose to create a custom implementation of the TextBox - call it a 'SubmitTextBox'
The custom SubmitTextBox could automatically hook up to the PreviewKeyDown event, and monitor the Enter key.
You could further adhere to MVVM by adding an ICommand DP to handle the 'Submit' event.
Something like this ...
public class SubmitTextBox : TextBox
{
public SubmitTextBox()
: base()
{
PreviewKeyDown += SubmitTextBox_PreviewKeyDown;
}
private void SubmitTextBox_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Enter)
{
if (this.SubmitCommand != null && this.SubmitCommand.CanExecute(this.Text))
{
// Note this executes the command, and returns
// the current value of the textbox.
this.SubmitCommand.Execute(this.Text);
}
}
}
/// <summary>
/// The command to execute when the text is submitted (Enter is pressed).
/// </summary>
public ICommand SubmitCommand
{
get { return (ICommand)GetValue(SubmitCommandProperty); }
set { SetValue(SubmitCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for SubmitCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SubmitCommandProperty =
DependencyProperty.Register("SubmitCommand", typeof(ICommand), typeof(SubmitTextBox), new PropertyMetadata(null));
}
And your XAML would end up looking like this:
<custom:SubmitTextBox
x:Name="txtFields"
Text="{Binding FieldsTextProperty}"
SubmitCommand="{Binding AddFieldCommand}"
Height="23"
TextWrapping="NoWrap"
Background="#FFCBEECD" />
Hope that helps :)
UPDATE: To clarify, the SubmitCommand I created returns the current text in the textbox as a parameter. In order to use this with the MVVM-Light toolkit, you'll need to create a RelayCommand that can accept the type 'string'.
public RelayCommand<string> AddFieldCommand { get; private set; }
public ViewModelConstructor()
{
AddFieldCommand = new RelayCommand<string>(AddField);
}
private void AddField(string text)
{
// Do something
}
I hope that clears things up a litte :)
I've created a custom user control. Is it possible for me to add a click event so that when someone clicks anywhere in the area of the control, a click event is fired?
The user control is defined as:
XAML:
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<StackPanel Orientation="Vertical">
<Image Source="{Binding TabItemImage}" HorizontalAlignment="Center" Stretch="None" VerticalAlignment="Top" />
<TextBlock Text="{Binding TabItemText}" FontSize="15" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
</StackPanel>
</Grid>
C#:
public partial class TabItem : UserControl
{
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("TabItemImage", typeof(string), typeof(TabItem), null);
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("TabItemText", typeof(string), typeof(TabItem), null);
public string TabItemImage
{
get { return (string)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public string TabItemText
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public TabItem()
{
InitializeComponent();
this.DataContext = this;
}
}
With the usage simply:
<tabs:TabItem TabItemText="OVERVIEW" TabItemImage="/Resources/Images/overview.png" />
Ideally I'd be able to modify the user control so that I could specify the click event, e.g.
<tabs:TabItem
TabItemText="OVERVIEW"
TabItemImage="/Resources/Images/options_64.png"
Click="TabItem_Clicked"/> <!-- when someone clicks the control, this fires -->
Is this possible? If so, what do I need to do to create a click event on a custom user control?
You need to add custom RoutedEvent to your TabItem UserControl, Below is code to add a Custom RoutedEvent:
public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent(
"Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TabItem));
public event RoutedEventHandler Click
{
add { AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
void RaiseClickEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(TabItem.ClickEvent);
RaiseEvent(newEventArgs);
}
void OnClick()
{
RaiseClickEvent();
}
And then in your UserControl InitializeMethod wire up PreviewMouseLeftButtonUp event to fire your Custom RoutedEvent:
PreviewMouseLeftButtonUp += (sender, args) => OnClick();
There is a pretty good How-to on MSDN discussing this, you might want to read that.
This answer by Suresh has a good point and that would be a great way to do it. However, If you don't have more than one click event for this UserControl, you can you just use the any of the number of mouseclick events that come with defining a custom UserControl.
I didn't know you could set the datacontext to its self... That is interesting.
XAML:
<UserControl x:Class="StackTest.TestControl"
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"
mc:Ignorable="d"
MouseLeftButtonUp="TestControl_OnMouseLeftButtonUp"
MouseDoubleClick="TestControl_OnMouseDoubleClick"
MouseLeftButtonDown="TestControl_OnMouseLeftButtonDown">
<Grid x:Name="LayoutRoot" Background="{StaticResource PhoneChromeBrush}">
<StackPanel Orientation="Vertical">
<Image Source="{Binding TabItemImage}" HorizontalAlignment="Center" Stretch="None" VerticalAlignment="Top" />
<TextBlock Text="{Binding TabItemText}" FontSize="15" HorizontalAlignment="Center" VerticalAlignment="Bottom" />
</StackPanel>
</Grid>
</UserControl>
CS:
public partial class TestControl : UserControl
{
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("TabItemImage" , typeof(string) , typeof(TabItem) , null);
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("TabItemText" , typeof(string) , typeof(TabItem) , null);
public string TabItemImage
{
get { return (string)GetValue(ImageProperty); }
set { SetValue(ImageProperty , value); }
}
public string TabItemText
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty , value); }
}
public TestControl()
{
InitializeComponent();
this.DataContext = this;
}
// or
private void TestControl_OnMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Add logic...
}
// or
private void TestControl_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// Add logic...
}
// or
private void TestControl_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
// Add logic...
}
}
This answer by Suresh requires the following namespace declarations:
using System;
using System.Windows;
using System.Windows.Controls;
namespace YourNameSpace
{
partial class OPButton
{
/// <summary>
/// Create a custom routed event by first registering a RoutedEventID
/// This event uses the bubbling routing strategy
/// see the web page https://msdn.microsoft.com/EN-US/library/vstudio/ms598898(v=vs.90).aspx
/// </summary>
public static readonly RoutedEvent ClickEvent = EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(OPButton));
/// <summary>
/// Provide CLR accessors for the event Click OPButton
/// Adds a routed event handler for a specified routed event Click, adding the handler to the handler collection on the current element.
/// </summary>
public event RoutedEventHandler Click
{
add {AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
/// <summary>
/// This method raises the Click event
/// </summary>
private void RaiseClickEvent()
{
RoutedEventArgs newEventArgs = new RoutedEventArgs(OPButton.ClickEvent);
RaiseEvent(newEventArgs);
}
/// <summary>
/// For isPressed purposes we raise the event when the OPButton is clicked
/// </summary>
private void OnClick()
{
RaiseClickEvent();
}
}
}
And the following References:
Windows;
PresentationCore;
WindowsBase;
I have a question regarding custom attached properties/events. In my scenario I want to attach a property/event to any control. The value of this property/event should be an event handler. In short, it should look like:
<TextBox local:Dragging.OnDrag="OnDrag" />
First I tried to implement OnDrag as an attached property. This works for the case above, but then the following case fails:
<Style TargetType="TextBox">
<Setter Property="local:Dragging.OnDrag" Value="OnDrag" />
</Style>
Because the "OnDrag" string can apparently not be made into a RoutedEventHandler (the attached property's type) by the XAML system.
The next thing I tried then was to try and use an attached event, very much like the builtin Mouse.MouseEnter for example.
The complete code for this is shown at the bottom. There are curious things happening with this version:
If you run the code as shown (with the RegisterRoutedEvent line commented) it will show the "Add handler" function is called. Then the xaml system has an internal exception when applying the style (due to missing registered event I guess).
If you run the code with the RegisterRoutedEvent line in effect everything runs, but the "Add handler" function is never called. I want it to be called though, so that I can register at the drag and drop manager.
Curiously, if I change the event in the EventSetter from my own to Mouse.MouseEnter the code that's automatically generated by the xaml designer (in MainWindow.g[.i].cs) is different.
I am not sure why 2) does not call the AddXYZHandler. MSDN seems to indicate this should work.
Finally my questions:
How can I make this work? Is it possible at all?
Do I better use an attached event or an attached property for my scenario?
in case of properties: How do I fix the Style Setter so it converts the OnDrag string to a proper RoutedEventHandler?
in case of events: What's going wrong here? Any way to fix this? I want AddXYZHandler to be called, but apparently that does not work with the style.
MainWindow.xaml:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GridTest.MainWindow"
xmlns:local="clr-namespace:GridTest"
Title="MainWindow" Height="350" Width="525"
local:XYZTest.XYZ="OnXYZAttached">
<Window.Style>
<Style TargetType="Window">
<EventSetter Event="local:XYZTest.XYZ" Handler="OnXYZStyle" />
</Style>
</Window.Style>
</Window>
MainWindow.xaml.cs:
using System.Windows;
namespace GridTest
{
public class XYZTest
{
//public static readonly RoutedEvent XYZEvent = EventManager.RegisterRoutedEvent("XYZ", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(XYZTest));
public static void AddXYZHandler(DependencyObject element, RoutedEventHandler handler)
{
MessageBox.Show("add handler");
}
public static void RemoveXYZHandler(DependencyObject element, RoutedEventHandler handler)
{
MessageBox.Show("remove handler");
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public void OnXYZAttached(object sender, RoutedEventArgs e)
{
MessageBox.Show("attached");
}
public void OnXYZStyle(object sender, RoutedEventArgs e)
{
MessageBox.Show("style");
}
}
}
}
New code:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="GridTest.MainWindow"
x:Name="root"
xmlns:local="clr-namespace:GridTest"
local:XYZTest.ABC="OnXYZTopLevel"
Title="MainWindow" Height="350" Width="525">
<ListBox ItemsSource="{Binding}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Background" Value="Red" />
<Setter Property="local:XYZTest.ABC" Value="OnXYZStyle" />
<!-- <Setter Property="local:XYZTest.ABC" Value="{Binding OnXYZStyleProperty, ElementName=root}" /> -->
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Window>
using System.Windows;
namespace GridTest
{
public class XYZTest
{
public static readonly DependencyProperty ABCProperty = DependencyProperty.RegisterAttached("ABC", typeof(RoutedEventHandler), typeof(XYZTest), new UIPropertyMetadata(null, OnABCChanged));
public static void SetABC(UIElement element, RoutedEventHandler value)
{
System.Diagnostics.Debug.WriteLine("ABC set to " + value.Method.Name);
}
static void OnABCChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
System.Diagnostics.Debug.WriteLine("ABC changed to " + ((RoutedEventHandler)e.NewValue).Method.Name);
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new[] { "A", "B", "C" };
}
public void OnXYZTopLevel(object sender, RoutedEventArgs e)
{
MessageBox.Show("handler top level");
}
public void OnXYZStyle(object sender, RoutedEventArgs e)
{
MessageBox.Show("handler style");
}
public RoutedEventHandler OnXYZStyleProperty
{
get { return OnXYZStyle; }
}
}
}
I successfully implemented drag and drop functionality completely using Attached Properties. If I were you, I'd avoid using custom events for this, as you're stuck with their parameters. Personally, I went for ICommand instead, but you could also use delegates.
Please look below at the list of properties and Commands that I used in my drag and drop base class implementation:
/// <summary>
/// Gets or sets the type of the drag and drop object required by the Control that the property is set on.
/// </summary>
public Type DragDropType { get; set; }
/// <summary>
/// Gets or sets the allowable types of objects that can be used in drag and drop operations.
/// </summary>
public List<Type> DragDropTypes { get; set; }
/// <summary>
/// Gets or sets the ICommand instance that will be executed when the user attempts to drop a dragged item onto a valid drop target Control.
/// </summary>
public ICommand DropCommand { get; set; }
/// <summary>
/// Gets or sets the DragDropEffects object that specifies the type of the drag and drop operations allowable on the Control that the property is set on.
/// </summary>
public DragDropEffects DragDropEffects { get; set; }
/// <summary>
/// The Point struct that represents the position on screen that the user initiated the drag and drop procedure.
/// </summary>
protected Point DragStartPosition
{
get { return dragStartPosition; }
set { if (dragStartPosition != value) { dragStartPosition = value; } }
}
/// <summary>
/// The UIElement object that represents the UI element that has the attached Adorner control... usually the top level view.
/// </summary>
protected UIElement AdornedUIElement
{
get { return adornedUIElement; }
set { if (adornedUIElement != value) { adornedUIElement = value; } }
}
The AdornedUIElement property holds an Adorner that displays the dragged items as they are dragged, but is optional for you to implement. In this base class, I have implemented most of the drag and drop functionality and exposed protected abstract methods that derived classes must implement. As an example, this method calls the OnAdornedUIElementPreviewDragOver method to provide derived classes an opportunity to change the behaviour of the base class:
private void AdornedUIElementPreviewDragOver(object sender, DragEventArgs e)
{
PositionAdorner(e.GetPosition(adornedUIElement));
OnAdornedUIElementPreviewDragOver(sender, e); // Call derived classes here <<<
if (e.Handled) return; // to bypass base class behaviour
HitTestResult hitTestResult = VisualTreeHelper.HitTest(adornedUIElement, e.GetPosition(adornedUIElement));
Control controlUnderMouse = hitTestResult.VisualHit.GetParentOfType<Control>();
UpdateDragDropEffects(controlUnderMouse, e);
e.Handled = true;
}
/// <summary>
/// Must be overidden in derived classes to call both the UpdateDropProperties and UpdateDragDropEffects methods to provide feedback for the current drag and drop operation.
/// </summary>
/// <param name="sender">The Control that the user dragged the mouse pointer over.</param>
/// <param name="e">The DragEventArgs object that contains arguments relevant to all drag and drop events.</param>
protected abstract void OnAdornedUIElementPreviewDragOver(object sender, DragEventArgs e);
Then in my extended ListBoxDragDropManager class:
protected override void OnAdornedUIElementPreviewDragOver(object sender, DragEventArgs e)
{
HitTestResult hitTestResult = VisualTreeHelper.HitTest(AdornedUIElement, e.GetPosition(AdornedUIElement));
ListBox listBoxUnderMouse = hitTestResult.VisualHit.GetParentOfType<ListBox>();
if (listBoxUnderMouse != null && listBoxUnderMouse.AllowDrop)
{
UpdateDropProperties(ListBoxProperties.GetDragDropType(listBoxUnderMouse), ListBoxProperties.GetDropCommand(listBoxUnderMouse));
}
UpdateDragDropEffects(listBoxUnderMouse, e);
e.Handled = true; // This bypasses base class behaviour
}
Finally, it is used simply in the UI like so (the RelativeSource declarations and narrow width here make it seem worse than it is):
<ListBox ItemsSource="{Binding Disc.Tracks, IsAsync=True}" SelectedItem="{Binding
Disc.Tracks.CurrentItem}" AllowDrop="True" Attached:ListBoxProperties.
IsDragTarget="True" Attached:ListBoxProperties.DropCommand="{Binding
DataContext.DropTracks, RelativeSource={RelativeSource AncestorType={x:Type
Views:ReleaseTracksView}}}" Attached:ListBoxProperties.DragDropTypes="{Binding
DataContext.DragDropTypes, RelativeSource={RelativeSource AncestorType={x:Type
Views:ReleaseTracksView}}}" Attached:ListBoxProperties.DragEffects="{Binding
DataContext.DragEffects, RelativeSource={RelativeSource AncestorType={x:Type
Views:ReleaseTracksView}}}">
I must be honest though... this was a lot of work. However, now that I can implement drag and drop operations with visual feedback just by setting a few properties, it totally seems worth it.