Handling Label's Content changed event in wpf using MVVM - c#

This label is present in a User Control and I am binding this Label's Content property to Window's datacontext. I want to execute a ICommand whenever a Content of this Label changes. Property it is binded to is in Parent's ViewModel. While I have a ICommand in user control's viewmodel.
<Label Style="{StaticResource LabelStyle}"
FontSize="28" HorizontalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window},
Mode=FindAncestor}, Path=DataContext.CurrentTag}">
</Label>

I found a cleaner approach that fits with MVVM pattern.
Interactivity is coming from below namespace
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Setting NotifyOnTargetUpdated=True, enables TargetUpdated event to trigger where I bound my ICommand
<Label Style="{StaticResource LabelStyle}" FontSize="28" HorizontalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}
,Mode=FindAncestor}
, Path=DataContext.CurrentTag, NotifyOnTargetUpdated=True}" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="TargetUpdated">
<i:InvokeCommandAction Command="{Binding SubmitCommand}"
CommandParameter="{Binding
RelativeSource={RelativeSource AncestorType={x:Type Window}
,Mode=FindAncestor}, Path=DataContext.CurrentTag}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Label>

The Label has no ContentChanged event to handle.
What you could do is to use a DependencyPropertyDescriptor and hook up an event handler using its AddValueChanged method programmatically:
public partial class MyUserControl : UserControl
{
public MyUserControl ()
{
InitializeComponent();
DataContext = new YourViewModelClass();
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(Label.ContentProperty, typeof(Label));
if (dpd != null)
{
dpd.AddValueChanged(label, OnLabelContentChanged);
}
}
private void OnLabelContentChanged(object sender, EventArgs e)
{
var vm = this.DataContext as YourViewModelClass;
vm.YourCommand.Execute(null);
}
}
Handling changes to dependency properties in the view: https://blog.magnusmontin.net/2014/03/31/handling-changes-to-dependency-properties/
<Label x:Name="label"
FontSize="28" HorizontalAlignment="Center"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window},
Mode=FindAncestor}, Path=DataContext.CurrentTag}">
</Label>

As mentioned before, Label has no ContentChanged event.
However, when I needed this property to work I have used SizeChanged Event instead.
What I did was whenever I change the Label's Content, I have changed the width of it with a value that close to original width.
Since Label does not show the edges, It does not matter that how much your Label long, unless you do not have any other tool near it.
I know this is not the exact and formal solution for this problem, but It saved my day.

Label has no ContentChanged event as already said but TextBox does have a TextChanged event.
If you disable the IsHitTestVisible and turn the BorderBrush and Background invisible then it looks almost the same as a Label and is not writeable so it also functions the same.

Related

Listbox selection hides other mouse event

Hello Stackoverflowers,
I have a System.Windows.Control.ListBox. It's doing a great job but i would like to had a few behaviours when i select certain types of items.
I can't do it in the bind property for SelectedItem because my Listbox's View Model (Foo) doesn't know all the needed datas for the work i want (some coming from another ViewModel : Bar).
My two mentioned ViewModel are field of a bigger class Zot, in order for Zot to access the content of both Foo and Bar
I foward click event in Foo and Bar to Zot using Interaction.Triggers, EventTrigger and InvokeCommandAction. It's working great for Bar (which is a canvas). However i have trouble with the Listbox.
After testing events SelectionChanged, MouseDown and Click, it appears that MouseDown is triggered if I click on the grid wrapping the listbox but not when i click on the ListBox. It feels like the embedded selection in the Listbox is conflicting with other events.
Anyone got any idea to do specific actions depending on the selected item, in a different viewmodel ?
Thanks a lot
EDIT :
Here is the XAML for the Listbox (in ToolboxView.xaml)
d:DataContext="{d:DesignInstance viewModels:ToolboxViewModel}">
<Grid>
<ListBox
ItemsSource="{Binding Tools}"
SelectedItem="{Binding SelectedTool}"
x:Name="ToolView" >
<ListBox.ItemTemplate>
<DataTemplate DataType="interfaces:IBuilder">
<TextBlock
FontWeight="DemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Here is the event on the Listbox, from the main window xaml (which view model holds the listbox view model, i explain why below). However the event is never triggered. Later in the same file, 3 similar event works perfectly (on a canvas). I tried to use MouseDown instead of SelectionChanged, it is triggered when i click in the grid containing the listbox but isn't trigger when i click listbox.
(in MainWindow.xaml)
<DockPanel>
<views:ToolboxView DockPanel.Dock="Left"
Width="120"
IsHitTestVisible="True"
DataContext="{Binding ToolBoxViewModel}"
x:Name="ToolboxView">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=DataContext.SelectionChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding ElementName=ToolboxOverlayView}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Now what i called "embeded selection" is the behaviour of the Listbox where i can highlight an element inside the listbox and select it. This works perfectly with the code above (i can select my tools, the property binded in ViewModel change accordingly). What i'm trying to do is firing the SelectionChanged event to do special work when a certain category of elements inside the listbox are selected.
I could do this in the setter of the property binded to Listbox's ItemSelected but the work to do need datas unknown from the listbox view model, which is why i have a mainwindow view model that holds the view model of the listbox and i try to get the SelectionChanged event in the main window view model (and another view model).
Tell me if it's not clear please.
You're trying to set a SelectionChanged event in your ToolboxView that does not know any SelectionChanged event.
You could create two DP in ToolboxView that stores the command and its parameter:
#region SelectionChangedCommand
public ICommand SelectionChangedCommand
{
get { return (ICommand)GetValue(SelectionChangedCommandProperty); }
set { SetValue(SelectionChangedCommandProperty, value); }
}
private readonly static FrameworkPropertyMetadata SelectionChangedCommandMetadata = new FrameworkPropertyMetadata {
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
public static readonly DependencyProperty SelectionChangedCommandProperty =
DependencyProperty.Register("SelectionChangedCommand", typeof(ICommand), typeof(ToolboxView), SelectionChangedCommandMetadata);
#endregion
#region SelectionChangedCommandParameter
public Object SelectionChangedCommandParameter
{
get { return (Object)GetValue(SelectionChangedCommandParameterProperty); }
set { SetValue(SelectionChangedCommandParameterProperty, value); }
}
private readonly static FrameworkPropertyMetadata SelectionChangedCommandParameterMetadata = new FrameworkPropertyMetadata {
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
public static readonly DependencyProperty SelectionChangedCommandParameterProperty =
DependencyProperty.Register("SelectionChangedCommandParameter", typeof(Object), typeof(ToolboxView), SelectionChangedCommandParameterMetadata);
#endregion
Then in the ToolboxView.xaml:
<Grid>
<ListBox
ItemsSource="{Binding Tools}"
SelectedItem="{Binding SelectedTool}"
x:Name="ToolView" >
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding Path=SelectionChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type ToolboxView}}}"
CommandParameter="{Binding Path=SelectionChangedCommandParameter, RelativeSource={RelativeSource AncestorType={x:Type ToolboxView}}}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate DataType="interfaces:IBuilder">
<TextBlock
FontWeight="DemiBold"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
And use it in MainWindow.xaml:
<views:ToolboxView DockPanel.Dock="Left"
Width="120"
IsHitTestVisible="True"
DataContext="{Binding ToolBoxViewModel}"
x:Name="ToolboxView"
SelectionChangedCommand="{Binding Path=DataContext.SelectionChangedCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
SelectionChangedCommandParameter="{Binding ElementName=ToolboxOverlayView}"/>

LostKeyboardFocus event not firing when textbox loses focus

I am using the MVVM pattern with the help of MVVM light. When I click a button I change the visibility property that the textboxs' visibility is bound to. When I lose focus, the trigger should execute and collapse the textbox. The problem is that the textbox never loses focus.
<TextBox x:Name="TextBox"
Text="{Binding TextBoxText, Mode=TwoWay}"
Visibility="{Binding TextBoxVisibility}"
Grid.Row="0"
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Height="30" Width="100"
TextWrapping="Wrap">
<i:Interaction.Triggers>
<i:EventTrigger EventName="LostKeyboardFocus">
<command:EventToCommand Command="{Binding CollapseTextBoxCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
CollapseTextBoxCommandchanges the TextBoxVisibility property to Visibility.Collapsed.
Here is the button
<Button Name="AddButton"
Command="{Binding ShowTextBoxCommand}"
Grid.Column="0" Grid.Row="1"></button>
ShowTextBoxCommand changes the TextBoxVisibility property to Visibility.Visibile.
Here is a recorded screencapture of the problem I'm talking about.
What should happen, is that when I click off the textbox, it should collapse the visibility.
Any help is much appreciated. Thanks.
I suggest looking at this older stackoverflow question TextBox LostFocus does not fire . I tried out a few things on a dummy project, and I couldn't get it to work with LostKeyboardFocus or PreviewLostKeyboardFocus as the other question suggests.
What I did try and I can confirm it worked, was to create an eventtrigger on the WPF window itself, to detect when clicking somewhere else other than the textbox:
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseUp">
<command:EventToCommand Command="{Binding ShowTextBoxCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Of course you can use MouseDown if you prefer.
I think it does nothing to the MVVM pattern, so I tried to replicate your issue simply using a Button, a CheckBox and a TextBox, where the CheckBox acts as a focusable control to help to remove the focus from the TextBox. Please try the following code snippet.
xaml:
<UniformGrid Columns="1">
<TextBox Name="textBox"
Text="Text is coming"
LostKeyboardFocus="TextBox_LostKeyboardFocus" />
<Button Content="getFocusButton"
Click="AddButton_Click" />
<CheckBox Content="dummyCheck" />
</UniformGrid>
xaml.cs:
private void TextBox_LostKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
(sender as TextBox).Visibility = Visibility.Collapsed;
}
private void AddButton_Click(object sender, RoutedEventArgs e)
{
textBox.Visibility = Visibility.Visible;
}
Based on your screen capture, the reason I think why your LostKeyBoardFocus event is not fired is because when your TextBox is focused, the black background you clicked is not focusable, so that the focus on TextBox is not lost at all.
Therefore, in my code snippet, I add a CheckBox. When TextBox is focused, its Visibilty will be collapsed once the CheckBox is clicked and focused.

EventTriggerBehavior Tapped not firing

I'm using MVVM-light approach for Pushpins on my Map.
I have bound the Tapped Event to a Command in my ViewModel.
However the event is not triggered.
All the other commands and properties bind perfectly.
I have also tried as an example to use a regular event, but it's also not firing.
My Command in my VM
private RelayCommand<Check> _showDetailCheckCommand;
public RelayCommand<Check> ShowDetailCheckCommand
{
get
{
Debug.WriteLine("Binded");
return _showDetailCheckCommand ?? (_showDetailCheckCommand = new RelayCommand<Check>((c) =>
{
Debug.WriteLine("Action!");
}));
}
}
In my XAML
<Page
x:Name="pageRoot"
...
<Maps:MapControl x:Name="Map" IsEnabled="False" Margin="0,8,0,8" MapServiceToken="******" LandmarksVisible="False" PedestrianFeaturesVisible="False" TrafficFlowVisible="True" ZoomLevel="16">
<!-- Incidents -->
<Maps:MapItemsControl ItemsSource="{Binding checks}">
<Maps:MapItemsControl.ItemTemplate>
<DataTemplate>
<Image Tag="{Binding}" Source="{Binding image_path}" Maps:MapControl.Location="{Binding geodata, Converter={StaticResource RoadsmartCoordinatesToGeoPointConverter}}" Maps:MapControl.NormalizedAnchorPoint=".5,.5" >
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Tapped">
<core:InvokeCommandAction Command="{Binding DataContext.ShowDetailCheckCommand, ElementName=pageRoot}" CommandParameter="{Binding}" />
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Image>
</DataTemplate>
</Maps:MapItemsControl.ItemTemplate>
</Maps:MapItemsControl>
</Maps:MapControl>
My output log does say:
'Binded'
But when I click on the image on the map the 'Action' is not executed.
Stupid of me.
I had the Map property isEnabled set to false.
Which is confusing because all the Map gestures work, but none of the binded XAML controls on top of it not.
Stupid mistake but nonetheless it might help people.

Binding to another control within the same DataTemplate

I have a ListBox whose DataTemplate holds a Button and Label:
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel>
<Button DockPanel.Dock="Left" Command="{Binding DataContext.AddToBoxCmd, RelativeSource={RelativeSource FindAncestor, AncestorType=UserControl, AncestorLevel=1}}"
CommandParameter=???/> <--How to bind command parameter here to Label's content below
<Label x:Name="PartNumberLabel" Content="{Binding Path=Element[PartNumber].Value}"/>
I would like to bind the Button's CommandParameter to the Label's Content value below. I would prefer to not use the SelectedItem property, as I'm using the selection highlight to indicate a different user interaction.
Thanks in advance!

How to fire a command on double-click listbox item using MVVM?

I'm trying to launch an ICommand when the user double-clicks on a listbox item. Also, I'm trying to do this using the MVVM pattern.
In this XAML, the key press "p" works perfectly. When I double click on the list box, the command never starts. I've set a break point to confirm "PlayVideoCommand" is not called with a double-click. Am I missing something or do I have to use Setter (which I'm not familiar with)?
<ListBox Name="SmallVideoPreviews" Grid.Column="1" MaxHeight="965"
ItemsSource="{Binding BrowseVideos}"
ItemTemplate="{StaticResource BrowseTemplate}">
<ListBox.InputBindings>
<KeyBinding Key="p"
Command="{Binding PlayVideoCommand}"
CommandParameter="{Binding ElementName=SmallVideoPreviews, Path=SelectedItem}"/>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding PlayVideoCommand}"
CommandParameter="{Binding ElementName=SmallVideoPreviews, Path=SelectedItem}"/>
</ListBox.InputBindings>
</ListBox>
Both double-click and "p" should execute the same command. When using the mouse, I can see the listboxitem is selected. I have a hunch that the MouseBinding Command property is not a dependency property but I don't know how to confirm this.
What's happening in your sample is that the listbox itself is reacting to the double click, but only in the part of it's area that is not covered by a list box item.
You need the event handler to be tied to the listboxitem.
Some ways to do it are here:
Double Click a ListBox item to open a browser
And some discussion about why a little code-behind in MVVM is not necessarily a terrible thing:
Firing a double click event from a WPF ListView item using MVVM
More discussion:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/9fb566a2-0bd6-48a7-8db3-312cd3e93340/
It seems that the ListBox doesn't handle double click on a ListBoxItem. This is a good answer:
Can't bind Command to ListBox
One could argue weather or not code-behind is terrible, but it ís possible use a command. Add the LeftDoubleClick gesture to the ItemTemplate like this:
<UserControl.Resources>
<DataTemplate x:Key="BrowseTemplate" >
<StackPanel >
<StackPanel.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding DataContext.PlayVideoCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Mode=OneWay}"
CommandParameter="{Binding }" />
</StackPanel.InputBindings>
<TextBlock Text="{Binding }" Width="50" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>

Categories

Resources