How do I set the MaxLength property of the DataGridTextColumn?
<tk:DataGridTextColumn Binding="{Binding Text}">
<tk:DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="MaxLength" Value="16"/>
</Style>
</tk:DataGridTextColumn.EditingElementStyle>
</tk:DataGridTextColumn>
You could also set it using the following behavior so you don't have to use a style and setters each time:
Public Class TextBoxBehavior
Private Shared Types As Type() = New Type() {GetType(AutoCompleteBox), GetType(ComboBox), GetType(DataGridTextColumn)}
Public Shared Function GetMaxLength(ByVal element As DependencyObject) As Integer
Return element.GetValue(MaxLengthProperty)
End Function
Public Shared Sub SetMaxLength(ByVal element As DependencyObject, ByVal value As Integer)
element.SetValue(MaxLengthProperty, value)
End Sub
Private Shared Sub ValidateElement(ByVal element As DependencyObject)
If element Is Nothing Then Throw New ArgumentNullException("element")
If Not Types.Contains(element.GetType) Then Throw New NotSupportedException("The TextBoxBehavior is not supported for the given element")
End Sub
Public Shared ReadOnly MaxLengthProperty As DependencyProperty =
DependencyProperty.RegisterAttached("MaxLength",
GetType(Integer), GetType(TextBoxBehavior),
New FrameworkPropertyMetadata(Integer.MaxValue, AddressOf TextBox_MaxLengthChanged))
Private Shared Sub TextBox_MaxLengthChanged(ByVal sender As Object, ByVal e As DependencyPropertyChangedEventArgs)
If sender Is Nothing Then Exit Sub
Dim value = DirectCast(e.NewValue, Integer)
If TypeOf sender Is AutoCompleteBox Then
Dim acb = DirectCast(sender, AutoCompleteBox)
If acb.IsLoaded Then
Dim tb = DirectCast(acb.Template.FindName("Text", acb), TextBox)
tb.MaxLength = value
Else
acb.AddHandler(AutoCompleteBox.LoadedEvent, New RoutedEventHandler(AddressOf Element_Loaded))
End If
ElseIf TypeOf sender Is ComboBox Then
Dim cb = DirectCast(sender, ComboBox)
If cb.IsLoaded Then
Dim tb = DirectCast(cb.Template.FindName("PART_EditableTextBox", cb), TextBox)
tb.MaxLength = value
Else
cb.AddHandler(ComboBox.LoadedEvent, New RoutedEventHandler(AddressOf Element_Loaded))
End If
ElseIf TypeOf sender Is DataGridTextColumn Then
Dim dgtc = DirectCast(sender, DataGridTextColumn)
Dim setter = GetIsMaxLengthSet(dgtc.EditingElementStyle)
If setter Is Nothing Then
Dim style = New Style(GetType(TextBox), dgtc.EditingElementStyle)
style.Setters.Add(New Setter(TextBox.MaxLengthProperty, value))
dgtc.EditingElementStyle = style
style.Seal()
Else
setter.Value = value
End If
End If
End Sub
Private Shared Function GetIsMaxLengthSet(ByVal style As Style) As Setter
If style Is Nothing Then Return Nothing
Dim setter = style.Setters.LastOrDefault(Function(s) TypeOf s Is Setter AndAlso DirectCast(s, Setter).Property Is TextBox.MaxLengthProperty)
If setter IsNot Nothing Then Return setter Else Return GetIsMaxLengthSet(style.BasedOn)
End Function
Private Shared Sub Element_Loaded(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim ml = GetMaxLength(sender)
TextBox_MaxLengthChanged(sender, New DependencyPropertyChangedEventArgs(TextBox.MaxLengthProperty, -1, ml))
sender.RemoveHandler(FrameworkElement.LoadedEvent, New RoutedEventHandler(AddressOf Element_Loaded))
End Sub
End Class
Usage:
<ComboBox xmlns:loc="MyNamesapace" loc:TextBoxBehavior.MaxLength="50" />
If you have a shared style among all columns, and you would like to add an additional style to one or more of those, you could use the Style.BasedOn Property:
<DataGridTextColumn Binding="{Binding SomeProperty, UpdateSourceTrigger=PropertyChanged}" ElementStyle="{StaticResource CellErrorStyle}">
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox" BasedOn="{StaticResource OriginalStyleKey}">
<Setter Property="MaxLength" Value="5" />
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
<Window.Resources>
<Style x:Key="sty_txtDesc" TargetType="TextBox">
<Setter Property="MaxLength" Value="495" />
</Style>
</Window.Resources>
for (int i = 0; i < miDataGridX.Columns.Count; i++)
{
if (miDataGridX.Columns[i].Header.ToString() == "Description")
{
((DataGridTextColumn)miDataGridX.Columns[i]).EditingElementStyle = (Style)this.FindResource("sty_txtDesc");
}
}
Related
I have create a Dependency Object Class:
Public Class TextMonitoring
Inherits DependencyObject
Public Shared ReadOnly MonitorTextProperty As DependencyProperty = DependencyProperty.RegisterAttached("MonitorText",
GetType(Boolean),
GetType(TextMonitoring),
New PropertyMetadata(False, New PropertyChangedCallback(AddressOf MonitorTextChanged)))
Public Shared Function GetMonitorTextProperty(sender As DependencyObject) As Boolean
Return CType(sender, PasswordBox).GetValue(MonitorTextProperty)
End Function
Public Shared Sub SetMonitorTextProperty(sender As DependencyObject)
CType(sender, PasswordBox).SetValue(MonitorTextProperty, True)
End Sub
Public Shared Function GetMonitorText(sender As DependencyObject) As Boolean
Return CType(sender, PasswordBox).GetValue(MonitorTextProperty)
End Function
Public Shared Sub SetMonitorText(sender As DependencyObject)
CType(sender, PasswordBox).SetValue(MonitorTextProperty, True)
End Sub
Public Shared Sub MonitorTextChanged(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
End Sub
End Class
My Style contains a Setter:
xmlns:local="clr-namespace:TestAttachedProperty">
<Style TargetType="{x:Type PasswordBox}">
<Setter Property="FontSize" Value="24" />
<Setter Property="Padding" Value="10" />
<Setter Property="Margin" Value="0 5 0 5" />
<Setter Property="local:TextMonitoring.MonitorText" Value="True" />
Compilation gives me an error: XDG0013: The property "MonitorText" does not have an accessible setter.
What am I doing wrong?
The Set* and Get* accessors should only set and get the value of the attached property:
Public Class TextMonitoring
Inherits DependencyObject
Public Shared ReadOnly MonitorTextProperty As DependencyProperty = DependencyProperty.RegisterAttached("MonitorText",
GetType(Boolean),
GetType(TextMonitoring),
New FrameworkPropertyMetadata(False, New PropertyChangedCallback(AddressOf MonitorTextChanged)))
Public Shared Sub SetMonitorText(ByVal element As DependencyObject, ByVal value As Boolean)
element.SetValue(MonitorTextProperty, value)
End Sub
Public Shared Function GetMonitorText(ByVal element As DependencyObject) As Boolean
Return CType(element.GetValue(MonitorTextProperty), Boolean)
End Function
Private Shared Sub MonitorTextChanged(sender As DependencyObject, e As DependencyPropertyChangedEventArgs)
End Sub
End Class
If I run my project, my tooltip converter is run once - I need it to run each time the mouse hoover over a row.
Here's my XAML:
<Window x:Class="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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:MyTooltipConverter x:Key="MyTooltipConverter" />
</Window.Resources>
<Grid>
<DataGrid x:Name="dataGrid" ItemsSource="{Binding}" HorizontalAlignment="Left" VerticalAlignment="Top" Height="263" Width="507">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Content="{Binding ??, Converter={StaticResource MyTooltipConverter}}" />
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
</Window>
And code...
Imports System.Globalization
Class MainWindow
Public Class Person
Public Property Name As String
End Class
Public Persons As New List(Of Person)
Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
For i As Integer = 0 To 5
Persons.Add(New Person With {.Name = "Test " + i.ToString})
Next
dataGrid.DataContext = Persons
End Sub
End Class
Public Class MyTooltipConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
If value Is Nothing Then
Return Nothing
End If
Dim panel As New StackPanel()
panel.Orientation = Orientation.Vertical
Dim block As New TextBlock()
block.Text = Now.ToString
panel.Children.Add(block)
Dim tip As New ToolTip()
tip.Content = panel
Return tip
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
How can I call MyTooltipConveter af get a Tooltip with the current time?
Thanks
Change your MyTooltipConverter.Convert method to Return panel. Returning tip throws a System.InvalidOperationException - 'ToolTip' cannot have a logical or visual parent.
As for the binding, just using the converter works.
<ToolTip Content="{Binding Converter={StaticResource MyTooltipConverter}}" />
Of course, for the time to update you have to move the mouse around to generate a new tooltip. If what you wanted was to have a tooltip that constantly updates then you need to add a timer and update the block.Text.
Something like this:
Public Class MyTooltipConverter
Implements IValueConverter
Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert
If value Is Nothing Then
Return Nothing
End If
Dim panel = New StackPanel()
panel.Orientation = Orientation.Vertical
Dim block As New TextBlock()
block.Text = Now.ToString
panel.Children.Add(block)
Dim timer As New System.Windows.Threading.DispatcherTimer()
timer.Interval = New TimeSpan(0, 0, 1)
AddHandler timer.Tick, Sub()
block.Text = Now.ToString
End Sub
timer.Start()
Debug.WriteLine("Timer Started")
AddHandler panel.Unloaded, Sub(s, e)
timer.Stop()
timer = Nothing
panel = Nothing
Debug.WriteLine("Timer Stopped")
End Sub
Return panel
End Function
Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack
Throw New NotImplementedException()
End Function
End Class
I'm trying to get an image displayed in the list of a combobox based on a bound boolean value. When the image is clicked the boolean value, and thus the image should change.
Here's the xaml:
<ComboBox Name="Combo2" Margin="20,79,20,0" ItemsSource="{Binding}" VerticalAlignment="Top" Height="20">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Name="MyBoolImage" Height="12" Width="12" MouseLeftButtonUp="Image_MouseLeftButtonUp"/>
<TextBlock Text="{Binding name}" Margin="5,0,0,0" Width="100" />
</StackPanel>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding LightOn}" Value="False">
<Setter TargetName="MyBoolImage" Property="Source" Value="/Images/Exit.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding LightOn}" Value="True">
<Setter TargetName="MyBoolImage" Property="Source" Value="/Images/Cut.png"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
And the data class:
Class ComboData
Private _LightOn As Boolean
Public Property LightOn As Boolean
Get
Return _LightOn
End Get
Set(value As Boolean)
_LightOn = value
End Set
End Property
Private _name As String
Public Property name As String
Get
Return _name
End Get
Set(value As String)
_name = value
End Set
End Property
Sub New(name_ As String, Light_On As Boolean)
_LightOn = Light_On
_name = name_
End Sub
End Class
loading some test data:
Dim x As New List(Of ComboData)
x.Add(New ComboData("test1a", True))
x.Add(New ComboData("test2a", False))
x.Add(New ComboData("test3a", True))
x.Add(New ComboData("test4a", True))
x.Add(New ComboData("test5a", False))
x.Add(New ComboData("test6a", True))
Combo2.ItemsSource = x
and finally the click event where the magic isn't happening...
Private Sub Image_MouseLeftButtonUp(sender As Object, e As MouseButtonEventArgs)
Dim SelectedComboData As ComboData = TryCast(CType(sender, Image).DataContext, ComboData)
SelectedComboData.LightOn = Not SelectedComboData.LightOn
e.Handled = True
End Sub
The LightOn value is changed as supposed to, even in the "x" (the list of combodata), the value is changed. But the displayed image is not changing.
What am I missing?
Thanks!
Class comboData should implement INotifyPropertyChanged in order to notify UI about its changes..
Class ComboData
Implements INotifyPropertyChanged
Private _LightOn As Boolean
Public Property LightOn As Boolean
Get
Return _LightOn
End Get
Set(value As Boolean)
_LightOn = value
OnPropertyChanged("LightOn")
End Set
End Property
Private _name As String
Public Property name As String
Get
Return _name
End Get
Set(value As String)
_name = value
OnPropertyChanged("name")
End Set
End Property
Sub New(name_ As String, Light_On As Boolean)
_LightOn = Light_On
_name = name_
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
I have a user control that as shown below, for a master detail sort of display. A typical MVVM architecture with a base view model that comes complete with a CloseCommand.
I am trying to scope a KeyBinding that will execute the close command on a TabItem, and just can't get it to work.
Interestingly, I can get it to work if I put the binding on the PersonDetailView (one of two possible USerControls that the TabControl might display, as shown below), but it should be on the TabControl or the Border that contains it.
Any suggestions?
Cheers,
Berryl
UserControl
<Grid>
<ListBox Style="{StaticResource ListBoxStyle}" />
<GridSplitter
HorizontalAlignment="Right" VerticalAlignment="Stretch" Grid.Column="1"
ResizeBehavior="PreviousAndNext" Width="5" Background="#FFBCBCBC" KeyboardNavigation.IsTabStop="False"
/>
<Border Grid.Column="2" Background="{StaticResource headerBrush}">
// ** THIS is the scope I want, but it doesn't work
<Border.InputBindings>
<KeyBinding Key="F4" Modifiers="Control" Command="{Binding CloseCommand}"/>
</Border.InputBindings>
<TabControl Style="{StaticResource TabControlStyle}" >
<TabControl.Resources>
<DataTemplate DataType="{x:Type personVm:PersonDetailVm}">
<local:PersonDetailView />
</DataTemplate>
<DataTemplate DataType="{x:Type orgVm:OrganizationDetailVm}">
<local:OrganizationDetailView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
</Border>
</Grid>
TabItem style
<Style x:Key="OrangeTabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border AllowDrop="true" ToolTip="{Binding DisplayName}">
<Border Name="Border" Background="Transparent" BorderBrush="Transparent" BorderThickness="1,1,1,0" CornerRadius="2,2,0,0">
<DockPanel x:Name="TitlePanel" TextElement.Foreground="{StaticResource FileTabTextBrush}">
<ctrl:GlyphButton
// ** This works as expected
Command="{Binding CloseCommand}" CommandParameter="{Binding}"
>
</ctrl:GlyphButton>
</DockPanel>
</Border>
// ** Can't get it to work from here either **
<Border.InputBindings>
<KeyBinding Command="{Binding CloseCommand}" Key="F4" Modifiers="Control" />
</Border.InputBindings>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
UPDATE
I am at a loss to set the RoutedCommand in my style
<Style x:Key="OrangeTabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="beh:RoutedCommandWire.RoutedCommand" Value="F4"/> **** ?? ****
<Setter Property="beh:RoutedCommandWire.ICommand" Value="{Binding CloseCommand}"/>
</Style>
Here is what I think the answer code looks like in C#
public class RoutedCommandWire
{
public static readonly DependencyProperty RoutedCommandProperty =
DependencyProperty.RegisterAttached("RoutedCommand", typeof(RoutedCommand), typeof(RoutedCommandWire), new PropertyMetadata(OnCommandChanged));
public static RoutedCommand GetRoutedCommand(DependencyObject d) { return (RoutedCommand) d.GetValue(RoutedCommandProperty); }
public static void SetRoutedCommand(DependencyObject d, RoutedCommand value) { d.SetValue(RoutedCommandProperty, value); }
public static readonly DependencyProperty ICommandProperty =
DependencyProperty.RegisterAttached("Iommand", typeof(ICommand), typeof(RoutedCommandWire));
public static ICommand GetICommand(DependencyObject d) { return (ICommand) d.GetValue(ICommandProperty); }
public static void SetICommand(DependencyObject d, ICommand value) { d.SetValue(ICommandProperty, value); }
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var fe = d as FrameworkElement;
if(fe==null) return;
if (e.OldValue != null) {
Detach(fe, (RoutedCommand) e.OldValue);
}
if (e.NewValue != null) {
Attach(fe, (RoutedCommand) e.NewValue, Execute, CanExecute);
}
}
private static void CanExecute(object sender, CanExecuteRoutedEventArgs e) {
var depObj = sender as DependencyObject;
if (depObj == null) return;
var command = GetICommand(depObj);
if (command == null) return;
e.CanExecute = command.CanExecute(e.Parameter);
e.Handled = true;
}
private static void Execute(object sender, ExecutedRoutedEventArgs e)
{
var depObj = sender as DependencyObject;
if (depObj == null) return;
var command = GetICommand(depObj);
if (command == null) return;
command.Execute(e.Parameter);
e.Handled = true;
}
public static void Detach(FrameworkElement fe, RoutedCommand command) {
var bindingCollection = fe.CommandBindings;
if (bindingCollection.Count == 0) return;
var matches = bindingCollection.Cast<CommandBinding>().Where(binding => binding.Equals(command));
foreach (var binding in matches) {
bindingCollection.Remove(binding);
}
}
public static void Attach(FrameworkElement fe, RoutedCommand command,
ExecutedRoutedEventHandler executedHandler, CanExecuteRoutedEventHandler canExecuteHandler, bool preview = false)
{
if (command == null || executedHandler == null) return;
var binding = new CommandBinding(command);
if (preview)
{
binding.PreviewExecuted += executedHandler;
if (canExecuteHandler != null)
{
binding.PreviewCanExecute += canExecuteHandler;
}
}
else
{
binding.Executed += executedHandler;
if (canExecuteHandler != null)
{
binding.CanExecute += canExecuteHandler;
}
}
fe.CommandBindings.Add(binding);
}
}
KeyBindings work only on controls which accept keyboard input. A Border doesn't. In general, InputBindings are also different from CommandBindings in that you can define a CommandBinding on a parent element so it handles commands when child elements have focus, but you can't define InputBindings on parent elements in order to have them effective on the child elements.
What you can do is to add a default InputGesture to your command's InputGestures collection. That seems to make the command available using that keyboard shortcut from every control that accepts keyboard input (that's much better than to have to specify InputBindings everywhere, isn't it?). In order to take advantage of this, you would have to use a RoutedCommand to invoke your MVVM-ICommand. You can combine the two using attached properties, in a pattern which I call "sticky command" and which is very similar to an attached behaviour.
This code defines the attached properties:
Public Class Close
Public Shared ReadOnly CommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("Command", GetType(RoutedCommand), GetType(Close), New PropertyMetadata(AddressOf OnCommandChanged))
Public Shared Function GetCommand(ByVal d As DependencyObject) As RoutedCommand
Return d.GetValue(CommandProperty)
End Function
Public Shared Sub SetCommand(ByVal d As DependencyObject, ByVal value As RoutedCommand)
d.SetValue(CommandProperty, value)
End Sub
Public Shared ReadOnly MVVMCommandProperty As DependencyProperty = DependencyProperty.RegisterAttached("MVVMCommand", GetType(ICommand), GetType(Close))
Public Shared Function GetMVVMCommand(ByVal d As DependencyObject) As ICommand
Return d.GetValue(MVVMCommandProperty)
End Function
Public Shared Sub SetMVVMCommand(ByVal d As DependencyObject, ByVal value As ICommand)
d.SetValue(MVVMCommandProperty, value)
End Sub
Private Shared Sub OnCommandChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
If e.OldValue IsNot Nothing Then
Detach(d, DirectCast(e.OldValue, RoutedCommand))
End If
If e.NewValue IsNot Nothing Then
Attach(d, DirectCast(e.NewValue, RoutedCommand), AddressOf DoCloseCommand, AddressOf CanDoCloseCommand)
End If
End Sub
Private Shared Sub CanDoCloseCommand(ByVal sender As Object, ByVal e As CanExecuteRoutedEventArgs)
If sender IsNot Nothing Then
Dim com As ICommand = GetMVVMCommand(sender)
If com IsNot Nothing Then
e.CanExecute = com.CanExecute(e.Parameter)
e.Handled = True
End If
End If
End Sub
Private Shared Sub DoCloseCommand(ByVal sender As Object, ByVal e As ExecutedRoutedEventArgs)
If sender IsNot Nothing Then
Dim com As ICommand = GetMVVMCommand(sender)
If com IsNot Nothing Then
com.Execute(e.Parameter)
e.Handled = True
End If
End If
End Sub
Public Shared Sub Detach(ByVal base As FrameworkElement, ByVal command As RoutedCommand)
Dim commandBindings As CommandBindingCollection = base.CommandBindings
If commandBindings IsNot Nothing Then
Dim bindings = From c As CommandBinding In commandBindings
Where c.Command Is command
Select c
Dim bindingList As New List(Of CommandBinding)(bindings)
For Each c As CommandBinding In bindingList
commandBindings.Remove(c)
Next
End If
End Sub
Public Shared Sub Attach(ByVal base As FrameworkElement, ByVal command As RoutedCommand, ByVal executedHandler As ExecutedRoutedEventHandler, ByVal canExecuteHandler As CanExecuteRoutedEventHandler, Optional ByVal preview As Boolean = False)
If command IsNot Nothing And executedHandler IsNot Nothing Then
Dim b As CommandBinding = New CommandBinding(command)
If preview Then
AddHandler b.PreviewExecuted, executedHandler
If canExecuteHandler IsNot Nothing Then
AddHandler b.PreviewCanExecute, canExecuteHandler
End If
Else
AddHandler b.Executed, executedHandler
If canExecuteHandler IsNot Nothing Then
AddHandler b.CanExecute, canExecuteHandler
End If
End If
base.CommandBindings.Add(b)
'For Each i As InputGesture In command.InputGestures
' GetInputBindings(base).Add(New InputBinding(command, i))
'Next
End If
End Sub
You'd use both of them on your TabItems, I guess, since that is where you want to handle the close command, and you would set Close.Command to a RoutedCommand which has the keyboard shortcut in its InputGestures, and Close.MVVMCommand="{Binding CloseCommand}".
UPDATE
You can define a RoutedCommand like this in your ViewModel:
Public Shared ReadOnly TestCommand As New RoutedUICommand("Test", "TestCommand", GetType(ViewModel))
Shared Sub New()
TestCommand.InputGestures.Add(New KeyGesture(Key.T, ModifierKeys.Control))
End Sub
The static constructor sets the default keygesture for the command. If you want to do that in XAML, you could also do that using custom attached properties. Anyway, you'd reference the RoutedCommand like this in XAML:
Close.Command="{x:Static my:ViewModel.TestCommand}"
Is there a nice way (except retemplating the whole TreeViewItem.Template) to disable selection in TreeView?
I am basically looking for the ItemsControl style of the TreeView (An ItemsControl is the best use to 'disable' selection on ListBox, read this post)
Try this:
<Trigger Property="HasItems" Value="true">
<Setter Property="Focusable" Value="false" />
</Trigger>
This did the trick for me (based on this answer, but no tied to item - selection is disabled whatsoever):
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter Property="Focusable" Value="False" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Based off of the links to the currently accepted answer, I implemented this in my project:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListView.ItemContainerStyle>
Works for TreeViewItem as well. And in the view model:
protected bool _DisableSelection;
private bool _IsSelected;
public bool IsSelected
{
get { return _IsSelected; }
set
{
if (value == _IsSelected) return;
_IsSelected = _DisableSelection ? false : value;
NotifyPropertyChanged();
}
}
Now you don't have to go hunting!
Whenever an item is selected, you could "unselect" it. Ex. modify the code from http://www.codeproject.com/KB/WPF/TreeView_SelectionWPF.aspx or use a MVVM approach (see http://www.codeproject.com/KB/WPF/TreeViewWithViewModel.aspx) and always set IsSelected back to false.
I decided to write a reusable behavior, HTH:
Namespace Components
Public NotInheritable Class TreeViewBehavior
Public Shared Function GetIsTransparent(
ByVal element As TreeViewItem) As Boolean
If element Is Nothing Then Throw New ArgumentNullException("element")
Return element.GetValue(IsTransparentProperty)
End Function
Public Shared Sub SetIsTransparent(ByVal element As TreeViewItem,
ByVal value As Boolean)
If element Is Nothing Then Throw New ArgumentNullException("element")
element.SetValue(IsTransparentProperty, value)
End Sub
Public Shared ReadOnly IsTransparentProperty As DependencyProperty =
DependencyProperty.RegisterAttached("IsTransparent", GetType(Boolean),
GetType(TreeViewBehavior),
New FrameworkPropertyMetadata(False,
AddressOf IsTransparent_PropertyChanged))
Private Shared Sub IsTransparent_PropertyChanged(
ByVal sender As Object, ByVal e As DependencyPropertyChangedEventArgs)
Dim tvi = DirectCast(sender, TreeViewItem)
Dim isTransparent = CBool(e.NewValue)
If isTransparent Then
AddHandler tvi.Selected, AddressOf tvi_Selected
Else
RemoveHandler tvi.Selected, AddressOf tvi_Selected
End If
End Sub
Private Shared Sub tvi_Selected(ByVal sender As Object,
ByVal e As RoutedEventArgs)
Dim treeViewItem = DirectCast(sender, TreeViewItem)
If Not treeViewItem.IsSelected Then Exit Sub
treeViewItem.Dispatcher.Invoke(
Sub(tvi As TreeViewItem) tvi.IsSelected = False,
System.Windows.Threading.DispatcherPriority.Send,
treeViewItem)
End Sub
End Class
End Namespace
Usage:
<Window xmlns:components="clr-namespace:WpfApplication.Components">
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<Setter
Property="components:TreeViewBehavior.IsTransparent"
Value="True" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
</Window>
I tried this and it worked for me. Because I have a simple and not dynamic treeview. But I think it can work by putting it in a style
<TreeViewItem ... Focusable="False" IsSelected="False"/>
I just unselected the TreeViewItems as they get selected.
I use TreeView only once. However if I added it in several places I would consider looking in to adding this to a Attached Behavior.
Xaml:
<TreeView SelectedItemChanged="TreeView_SelectionChanged">
Code behind:
private void TreeView_SelectionChanged(object sender, RoutedEventArgs e)
{
if (!(sender is TreeView myTreeView)) return;
var selectedItem = (TreeViewItem)myTreeView.SelectedItem;
if (selectedItem == null) return;
selectedItem.IsSelected = false;
}
I did this a differently than the accepted answer:
Lets say that you have a property in your ViewModel (say 'ShouldPreventSelection')
Now when ShouldPreventSelection is true you want selection to be disabled:
In your TreeView fire the PreviewSelected event like so:
<TreeView Name="TreeView1"
...
PreviewSelected="TreeView1_PreviewSelected"
..
/>
Then in the codebehind you can the following:
private void TreeView1_PreviewSelected(object sender, RoutedEventArgs e)
{
MyViewModel myViewModel = TreeView1.DataContext as MyViewModel;
if (myViewModel == null)
{
return;
}
if (myViewModel .ShouldPreventSelection)
{
e.Handled = true;
}
}