UWP: Highlight ListBoxItem - Events That Coincide with the Highlighting - c#

I have a ListBox bound to a collection of items of type Definition. My requirement is that every time the mouse is hovered over the area of a templated ListBoxItem, a second ListBox open next to the ListBoxItem, revealing sub-items which are of type Word.
(I am basically implementing something similar to a TreeView using two ListBoxes. This is for earlier versions so using a TreeView control is not an option.)
This is the data structure...
public class Word
{
public string Name { get; set; }
}
public class Definition
{
public string Name { get; set }
public ObservableCollection<Word> Words;
}
public class Dictionary
{
public string Name { get; set }
public ObservableCollection<Definition> Definitions;
}
And here is the XAML view...
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Grid.Row="0"
Height="0">
<Button.Flyout>
<Flyout x:Name="DefinitionFlyout"/>
<ListBox x:Name="WordsListBox" HorizontalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemp late x:DataType="local:Word">
<TextBox TextWrapping="NoWrap"
Height="Auto"
BorderThickness="0"
HorizontalAlignment="Stretch"
Text="{x:Bind Name}"
TextAlignment="Left"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Flyout>
</Button.Flyout>
</Button>
<ListBox x:Name="DefinitionsListBox"
Grid.Row="1"
SelectionMode="Single"
HorizontalAlignment="Stretch">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate x:DataType="local:Definition">
<TextBox TextWrapping="NoWrap"
Height="Auto"
BorderThickness="0"
HorizontalAlignment="Stretch"
Text="{x:Bind Name, Mode=TwoWay}">
</TextBox>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
When the mouse pointer hovers over a Definition item in DefinitionsListBox, WordsListBox should fly out and display the Words of that Definition. And when the pointer exits that Definition and hovers over a new one, I want WordsListBox to reflect that change.
Unfortunately, I can't find the events that will help me accomplish this.
I thought defining PointerEntered and PointerExited in the TextBox of Definition would do the trick but they don't because PointerExited fires IMMEDIATELY after PointerEntered, as in almost simultaneously, and not when the mouse exits the TextBox area. And SelectionChanged of ListBox doesn't fire.
The first event should fire when ListBoxItem highlighting begins, and the second one, when the highlighting ends.
What do you recommend for this, please?

I thought defining PointerEntered and PointerExited in the TextBox of Definition would do the trick but they don't because PointerExited fires IMMEDIATELY after PointerEntered, as in almost simultaneously.
The problem is the when Flyout show at the button there is a mask layer cover on the window. This will prevent basic input event of TextBox defined. It looks PointerExited fires immediately after PointerEntered as in almost simultaneously.
For solving this , you could set OverlayInputPassThroughElement property for Flyout that make the area of ListBox could response PointerEntered PointerExited event when Flyout is opened. For more please refer the following code.
<Flyout x:Name="DefinitionFlyout" OverlayInputPassThroughElement="{x:Bind DefinitionsListBox}">

Related

Lock zIndex of a ContentPresenter Upon Mouse Enter or Drag Complete

I need to lock the Z-order of a canvas/content control after it is dragged by a Thumb.
In the below image, the "Joe Smith" pops above the others the other two ellipses while the the mouse over is active. Once the drag stops and the mouse moves out, it drops back to its value.
I am unable to find a solution within the design I have shown below to keep it locked above the others.
Minimal Reproducible Example
I have created a code gist of all code that contains the xaml, the thumb class and the people class. All one has to do is create a .Net Core WPF app and drop in the snippets of code and set name spacing in Xaml as needed.
Design
There is an ItemsControl which has DataTemplate defined with a Canvas. Each content in the ItemsControl has ContentControl which has a Thumb as applied by a style.
Another style trigger of the mouse entering the ContentPresenter has the content temporarily pushed in zIndex above the others by setting the content's internal Grid's zIndex to 1.
How to stick that zIndex?
Xaml
<ItemsControl Margin="10" ItemsSource="{Binding Source={StaticResource People}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Canvas>
<ContentControl Width="100" Height="100" >
<Grid>
<Ellipse Fill="Silver">
<Ellipse.Effect>
<DropShadowEffect Color="Black" Direction="320" ShadowDepth="6" Opacity="0.5"/>
</Ellipse.Effect>
</Ellipse>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Margin="3,3,3,0" Text="{Binding Path=First}"/>
<TextBlock Margin="3,0,3,7" Text="{Binding Path=Last}"/>
</StackPanel>
</Grid>
</ContentControl>
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Grid.ZIndex" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
See the Gist for all supporting classes and styles to reproduce
Actual Design
The ultimate goal is to have a panel of images of a set size, when the user grabs the thumb the image it will move forward and lock above the others. I say this in-case there is another way to do that which could provide an answer above the minimal example design.
In my ItemsControl I changed the ItemsPanelTemplate to be a Canvas such as
<ItemsPanelTemplate>
<Canvas x:Name="MainCanvas" />
</ItemsPanelTemplate>
Which when looking at the Visual Tree where the user was clicking the ContentControl, it had a parent of a Canvas that had a ContentPresenter with that top level Canvas such as (see named MainCanvas):
I changed the ContentControl to have a MouseEnter event :
<ContentControl Width="100" Height="100" MouseEnter="EnterMouse">
<Grid>
<Ellipse Fill="Silver">
In that method I needed to find the named "MainCanvas" Canvas and enumerate all of its children the ContentPresenters and extract the max ZIndex and then set my ContentControl (shown in blue above) to that ZIndex value plus 1.
Here is the code behind where extract the necessary the parents:
private void EnterMouse(object sender, MouseEventArgs e)
{
if (sender is ContentControl cc)
{
var cpParent = FindParent<ContentPresenter>(cc);
var p2 = FindParent<Canvas>(cpParent);
var max = p2.Children.Cast<UIElement>().Max(control => Panel.GetZIndex(control));
Panel.SetZIndex(cpParent, max + 1);
}
}
private T FindParent<T>(DependencyObject child) where T : DependencyObject
{
DependencyObject immediateParent = VisualTreeHelper.GetParent(child);
T parent = immediateParent as T;
return parent ?? FindParent<T>(immediateParent);
}

How to stop Scroll changed event from triggering on data binding (WPF)

</ItemsControl.ItemTemplate>
<ItemsControl.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="GroupItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="GroupItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" HorizontalAlignment="Center" FontWeight="Bold" Foreground="{StaticResource Accent}" Text="{Binding Path=Name , StringFormat={}{0:D}}" />
<ItemsPresenter Grid.Row="1"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ItemsControl.GroupStyle>
</ItemsControl>
<Interactivity:Interaction.Triggers>
<Interactivity:EventTrigger EventName="ScrollChanged" >
<Presentation:InvokeDelegateCommandAction Command="{Binding ChatScrollViewer_OnViewChange}" CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=InvokeParameter}" />
</Interactivity:EventTrigger>
</Interactivity:Interaction.Triggers>
</ScrollViewer>
private void ChatScrollViewer_OnViewChangeEvent(ScrollChangedEventArgs Event)
{
ScrollViewer scrollViewer = Event.OriginalSource as ScrollViewer;
if (scrollViewer.VerticalOffset == 0)
{
}
}
When data binding take place scroll changed event is triggered . How check that this is a trigger from data binding and do nothing and when triggered manually do something
When the binding is updated, is it the ItemsSource of the scroll viewer?
Chances are the binding is adding or removing something, resulting in the scroll viewer changing the size of it's content. Take a look at the scroll changed event args, there's lot of conditions that trigger this event.
You probably only want to check the event properties like:
VerticalChange - Gets a value that indicates the change in vertical offset of a ScrollViewer.
So try only performing your action when VerticalChange != 0. Or drop a debugger in there and see if there's any specific event types you want to ignore that only happen when binding changes, for example:
ExtentHeightChange - Gets a value that indicates the change in height of the ScrollViewerextent.
Might only be none-zero when the binding has updated, making the scroll bar bigger (but not changing the scroll position).
So something like this:
private void ChatScrollViewer_OnViewChangeEvent(ScrollChangedEventArgs Event)
{
if (Event.VerticalChange != 0)
{
ScrollViewer scrollViewer = Event.OriginalSource as ScrollViewer;
if (scrollViewer.VerticalOffset == 0)
{
}
}
}
Hope that helps.

WPF Binding TextBox width to grid column within a list box

I'm trying to build a simple (in terms of design) screen which will allow users to enter multiple rows of data.
The format of the screen is 4 columns (TextBox, TextBox, ComboBox, TextBox), with new rows added dynamically.
I have tried 2 approaches initially - one using a DataGrid, and one using a ListView.
The DataGrid caused many headaches with items retaining focus when they were clicked out of, and the ListView prevented me from being able to consistently access the underlying cell, instead returning the bound data object represented by the row.
My current approach uses a custom component which represents a row, and contains the 3 TextBox objects, and one ComboBox object.
One or more of these objects are displayed in a ListBox.
This approach allows for handling events more consistently, but is visually less straightforward to get working.
Currently, when a row is displayed, the first 3 columns in the grid (which have an explicitly defined width) display fine, but the textbox in the final column does not expand to fill the available width.
The relevant code for the panel:
<DockPanel LastChildFill="True" Name="basePanel">
<Grid x:Name="baseGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60" />
<ColumnDefinition Width="60"></ColumnDefinition>
<ColumnDefinition Width="55"></ColumnDefinition>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBox Name="txtYear" Grid.Column="0" Style="{StaticResource txtComponentStyle}" Text="{Binding Path=RatingYear, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Name="txtArrears" Grid.Column="1" Style="{StaticResource txtComponentStyle}" Text="{Binding Path=ArrearsAmount, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
<ComboBox Name="cmbChangeCode" Grid.Column="2" ItemsSource="{Binding Path=ChangeCodes, Mode=OneTime}" SelectedItem="{Binding Path=ChangeCode}" />
<TextBox Name="txtComments" Grid.Column="3" Style="{StaticResource txtComponentStyle}" Text="{Binding Path=Comments, ValidatesOnDataErrors=True,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Stretch" />
</Grid>
</DockPanel>
When I select a row in the ListBox, I can see that the object itself fills the entire width, yet the text box in the final column only expands to fit the content.
Setting the width in the column definition to * should (as I understand it) cause the column to expand to fille the available space, and setting the HorizontalAlignment property on the textbox to Stretch should cause the text box to fill the column.
The code below creates the ListBox.
<UserControl.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="0.5" Margin="1">
<DockPanel LastChildFill="True" HorizontalAlignment="Stretch" Background="DarkSlateBlue">
<Controls:ArrearsListEntry />
</DockPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<DockPanel LastChildFill="True" Background="Red">
<ListBox IsSynchronizedWithCurrentItem="True" Name="lstRowData" ItemsSource="{Binding Path=Rows}" />
</DockPanel>
Is there a way to bind the width of the text box to the actual width of the column, or a property which will cause the text box to automatically expand to the available width?
The ListBoxItem's Content is left aligned by default. Add a Setter for the HorizontalContentAlignment to your ListBoxItem Style:
<Style TargetType="ListBoxItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ContentTemplate">
...
</Setter>
</Style>

VisualBrush (linked to VLC) doesn't change on TabControl selection change

I am having a weird experience with a combination of visual brush (linked to a VLC player through VLC.DotNet) and a tab control. I have created a custom control using the VLC player to watch an RTSP stream and have multiple of these controls in a given window.
The problem is that if I put all the controls in a list view they all display properly. But if I put them in a tab control then it always shows the stream of the first-selected tab item, not matter what tab I'm currently on. Everything else in the control (label, etc.) changes properly, but not the part drawn by the visual brush.
The view for my control is defined as:
<UserControl x:Class="myApp.View.CameraMonitorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Wpf="clr-namespace:Vlc.DotNet.Wpf;assembly=Vlc.DotNet.Wpf"
Loaded="UserControl_Loaded">
<DockPanel>
<Label Content="{Binding Name}" DockPanel.Dock="Bottom"/>
<Grid Margin="3">
<Grid.Background>
<VisualBrush Stretch="Uniform">
<VisualBrush.Visual>
<Image Source="{Binding VideoSource, ElementName=vlcControl}"/>
<!--<Image Source="{Binding Image}" /> -->
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
<Wpf:VlcControl x:Name="vlcControl" Margin="3"/>
</Grid>
</DockPanel>
</UserControl>
The code behind for the view starts playing the RTSP, but I don't think that code will help with this problem.
Meanwhile the ViewModel (stripped down for ease of viewing) is just:
class CameraMonitorViewModel : ViewModelBase
{
public CameraMonitorViewModel(string name, string image)
{
Name = name;
Image = image;
}
public string Name {get; set;}
public string Image { get; set; }
}
And I have a data template defined as:
<DataTemplate DataType="{x:Type vm:CameraMonitorViewModel}">
<v:CameraMonitorView HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</DataTemplate>
The full window view model has an ObservableCollection called Monitors and the view displays:
<TabControl ItemsSource="{Binding Monitors}" SelectedIndex="0" Height="300">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
<ListView ItemsSource="{Binding Monitors}" Height="300" />
The ListView properly shows different images from each camera. The tab item will always show the same camera, but the control's label item changes when I clicked different tabs. Moreover, if I replace the databinding to the VLC control's image to the commented out static image object (whose source is set by way of the view model) then the image will properly change when I click different tabs.
I'm really confused and would appreciate any help that could be provided.
Yeah, as I noted in the comment to the question the problem was that I was starting the stream playing based on the Loaded event for the control. But it seems like it doesn't get fired after the second tab, probably because of what Rachel mentions here: Loaded event doesn't fire for the 4th TabControl tab (being that it reuses the template rather than loading multiple ones).

Binding command to a templated user control

I'm new to WPF, but have been able make a lot of progress in short time thanks to a good book on the topic, and of course, quality posts on sites like this one. However, now I've come across something I can seem to figure out by those means, so I posting my first question.
I've have a ControlTemplate in a resource dictionary which I apply to several UserControl views. The template provides a simple overlay border and two buttons: Save and Cancel. The templated user control holds various text boxes, etc., and is bound to some ViewModel depending on the context. I'm trying to figure out how to bind the commands to the Save/Cancel buttons when I use/declare the UserControl in some view. Is this is even possible, or am I doing something very wrong?
First, the template:
<ControlTemplate x:Key="OverlayEditorDialog"
TargetType="ContentControl">
<Grid>
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="DarkGray"
Opacity=".7"/>
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="DarkGray">
<Grid>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ContentPresenter Grid.Row="0"/>
<Grid Grid.Row="1"
Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="1"
Content="Cancel"
***Command="{Binding CancelCommand}}"**
/>
<Button Grid.Column="0"
Content="Save"
***Command="{Binding Path=SaveCommand}"***/>
</Grid>
</Grid>
</Border>
</Grid>
</ControlTemplate>
The template in turn is used in the CustomerEditorOverlay user control
<UserControl x:Class="GarazhApp.View.CustomerEditorOverlay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
<UserControl.Resources>
<ResourceDictionary Source="Dictionary1.xaml"/>
</UserControl.Resources>
<ContentControl Template="{StaticResource ResourceKey=OverlayEditorDialog}">
<Grid Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
</Grid.RowDefinitions>
<SomeElement/>
<SomeOtherElement/>
</Grid>
</ContentControl>
...and finally, the user control is used as part of a view like so:
<local:CustomerEditorOverlay Visibility="{Binding Path=CustomerViewModel.ViewMode, Converter={StaticResource myConverter}, FallbackValue=Collapsed}"
d:IsHidden="True" />
So, based on what I've learned from a project I have been on forever and a half, we have a workable pattern.
Let's say you have a bunch of modal windows that all get applied the same style within the application. To have Save and Cancel buttons on each view, the UserControl used for all of the modal windows has several dependency properties. In addition, we specify virtual methods for your commands (e.g. OnSaveCommand, OnCancelCommand, CanExecuteSaveCommand, CanExecuteCancelCommand) and the commands themselves as properties in a base ViewModel that is inherited by your views.
Ultimately, what happens is we create new modal windows by simply doing this:
<my:YourBaseView x:class="MyFirstView" xmlns:whatever="whatever" [...]>
<my:YourBaseView.PrimaryButton>
<Button Content="Save" Command="{Binding SaveCommand}" />
</my:YourBaseView.PrimaryButton>
<!-- some content -->
</my:YourBaseView>
With accompanying code-behind:
public class MyFirstView : YourBaseView
{
[Import] /* using MEF, but you can also do MvvmLight or whatever */
public MyFirstViewModel ViewModel { /* based on datacontext */ }
}
And a ViewModel:
public class MyFirstViewModel : ViewModelBase
{
public override OnSaveCommand(object commandParameter)
{
/* do something on save */
}
}
The template for this UserControl specifies ContentControls in a grid layout with the Content property bound to the PrimaryButton and SecondaryButton. Of course, the content for the modal is stored in the Content property of the UserControl and displayed in a ContentPresenter as well.
<Style TargetType="{x:Type my:YourBaseView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:YourBaseView}">
<Grid>
<!-- ignoring layout stuff -->
<ContentControl Content="{TemplateBinding Content}" />
<ContentControl Content="{TemplateBinding PrimaryButton}" />
<ContentControl Content="{TemplateBinding SecondaryButton}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
UserControl code:
public class YourBaseView : UserControl
{
public static readonly DependencyProperty PrimaryButtonProperty =
DependencyProperty.Register("PrimaryButton", typeof(Button), typeof(YourBaseView), new PropertyMetadata(null));
public Button PrimaryButton
{
get { return (Button)GetValue(PrimaryButtonProperty); }
set { SetValue(PrimaryButtonProperty, value); }
}
/* and so on */
}
You can change the style for each instance of your templated view, of course. We just happen to stick with one base style.
TL;DR edit: I may have gone a bit overboard since I think you just need the understanding that exposing dependency properties of type Button which are set up through the XAML each time you create a new overlay. That, or you could probably RelativeSource your way back up to the visual tree with something like {Binding DataContext.SaveCommand, RelativeSource={RelativeSource AncestorType={x:Type MyView}}} but it's a little dirtier.

Categories