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);
}
Related
I have this ItemsControl:
<ItemsControl Grid.Row="1" ItemsSource="{Binding Board}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:Square">
<Rectangle Stroke="Blue" StrokeThickness="0.5" Width="{Binding Width}" Height="{Binding Height}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
and it simply draws squares onto the screen. I want an event so that when I click on one of the squares it calls that event, I need to also get the object I clicked on (the data type of the template is a Square class and the entire grid is bound to an observable collection called Board), how could I do that?
Put the rectangle in the template of a Button and handle the button's click event. Remember to set the rectangle's Fill to Transparent, or else mouse clicks in the fill area of the button won't be detected.
<Button Click="Rectangle_Click">
<Button.Template>
<ControlTemplate TargetType="Button">
<Rectangle
Fill="Transparent"
Stroke="Blue"
StrokeThickness="0.5"
Width="{Binding Width}"
Height="{Binding Height}"
/>
</ControlTemplate>
</Button.Template>
</Button>
private void Rectangle_Click(object sender, RoutedEventArgs e)
{
var button = sender as Button;
var square = button.DataContext as Square;
// Do whatever
}
It would be preferable to give Square a command property, and bind Button.Command to that:
public class Square
{
// stuff
public ICommand SelectCommand { get; } // Initialize in constructor
// stuff
}
<Button Command="{Binding SelectCommand}">
<!-- ...as before... -->
But then you need to implement ICommand, etc. The Click event works well enough.
You could also handle MouseLeftButtonDown on the Rectangle itself. You'd still have to set its Fill to Transparent. I prefer this solution because Click behavior is more complicated than MouseLeftButtonDown: For example, when you mouse down on
a button and drag out of the button before releasing the mouse button, Click isn't raised. Users are accustomed to that behavior.
I've tried several different solutions to this, but can't land on one that meets all of my needs.
We have an observable collection of objects that each have a status and a name. It's a sort of task-list of running items. To display this list in WPF, we have some code that represents each item as an ellipse with some colors and animations.
The problem is that we want to display the name of the item as a 'popup' both on mouseover, or when the task is in a given state.
Attempt #1
My first attempt implemented this as a Datatemplate (to be used as an ItemTemplate) with an actual WPF Popup. I implemented two datatriggers - one for mouseover and one for task state. I positioned the popup based on my ellipse and everything was great. However, moving the window or switching to a different window left the popup on top of everything.
Attempt #2
Instead of using the popup I used a textbox in a canvas. This works great until the Datatemplate is used in the Listbox. The item host (stackpanel) ends up clipping the string.
Here's example code:
<DataTemplate x:Key="EllipseTemplate">
<Grid Height="40" Width="40">
<Canvas Name="PopupCanvas" HorizontalAlignment="Center" Width="500">
<TextBlock Name="PopupName"
Width="{Binding ElementName=PopupCanvas, Path=ActualWidth}"
Text="{Binding}"
Background="Transparent"
FontSize="16" HorizontalAlignment="Center" TextAlignment="Center" FontWeight="Bold"
Canvas.Top="-25"
Visibility="Collapsed"
/>
</Canvas>
<Ellipse x:Name="Ellipse" Height="25" Width="25" Margin="0"
HorizontalAlignment="Center" VerticalAlignment="Center"
Fill="Green"
RenderTransformOrigin="0.5, 0.5" StrokeThickness="0.5" Stroke="Black">
</Ellipse>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsMouseOver, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
<Setter TargetName="PopupName" Property="Visibility" Value="Visible" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
<Grid Name="Test" Background="LightGoldenrodYellow" ClipToBounds="False" Margin="50">
<ListBox Name="OverlayTest"
Background="CornflowerBlue"
BorderThickness="0"
VerticalAlignment="Center"
Margin="10"
ClipToBounds="False"
ItemTemplate="{StaticResource EllipseTemplate}">
<sys:String>Very long string that will get clipped</sys:String>
<sys:String>Two</sys:String>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" IsItemsHost="True" Margin="10,50,10,50" ClipToBounds="False"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
</Grid>
Attempt #3
I moved my canvas/textbox outside of the datatemplate and create a grid to put it above the listbox of ellipses. This works from a layout perspective, but creates a big mess in terms of checking for mouseover and centering the textbox on the control that's active/hovered.
So that leaves me without an implementation that works the way I want. Anyone have any suggestions?
Ok here I have another idea. I had problem with the ListBox before. Try replacing the ListBox with an ItemsControl.
Attempt #2 sounds like is working fine. You should be able to solve the issue of the clipping using one of these solutions (or all of them):
Set the ClipToBounds property of the ListBox to false
Set the ClipToBounds property of the Stackpanel to false
Scenario:
I am working on a Store App in WinRT/Win8.
I have a ScrollView, with a custom UserControl child inside - all as part of one "main" UserControl.
When the main UserControl (with the ScrollView -> child UserControl) in is Initialized/navigated to - even with the App width not full-screen; the UserControl is at the full width of the ScrollView - as desired. Images below:
Image 1 - main User Control Opens with Window Fullscreen
Image 2 - main User Control Opens Starts Half-Width (or any width)
The ScrollView itself is within a Grid and keeps with the full width of the App window, even when it's resized - as desired.
Issue:
The issue I'm having is that when I resize the App window horizontally, the child UserControl does not keep the same width as its parent ScrollView's.
This causes the ScrollView to then have Horizontal Scrollbars - which I do not want.
Image 3 - Window Horizontal Width Resized
I want to keep the width of the child to be bound inside the width of the ScrollView with no Horizontal Scrollbars (as in Image 2).
Markup is similar to this (I have stripped down for readability):
<Grid>
<!-- Some row/column definitions in here -->
...
<!-- A header TextBlock -->
...
<ScrollViewer x:Name="scrlTableRows" Grid.ColumnSpan="3" Grid.Row="1"
HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto"
Padding="66,0,66,40" ZoomMode="Disabled">
<local:MyCustomUserControl Margin="0,10,0,10" HorizontalAlignment="Stretch" />
</ScrollViewer>
...
<!-- Just a button here-->
</Grid>
I have already tried setting (on the child custom UserControl):
Width="{Binding Path=ActualWidth, ElementName=scrlTableRows}"
The child is not set at the full width of the ScrollView to start with (which is what I need), and doesn't resize the width with its parent either - giving me scrollbars
Width="{Binding Path=Width, ElementName=scrlTableRows}" The child does start at the full width of the parent, but doesn't resize - giving me scrollbars
I have also tried placing the UserControl inside a Grid (within the ScrollView), amongst many other HorizontalAligment and Width properties.
All to no avail.
No other similar situations/answers have worked from other helpful fellows at StackOverflow.
Obviously, I need the vertical scrollbars - as is kind of evident; before anyone asks.
Can anybody give me any pointers, please?
Update:
Here is the custom UserControl's Xaml, as requested by #LovetoCode:
<UserControl *usual user control declaritive stuff in here*>
<UserControl.Resources>
<CollectionViewSource x:Key="FieldViewModelsSource" Source="{Binding ItemToEdit.FieldViewModels}"/>
<datatemplateselectors:FieldViewModelDataTemplateSelector
x:Key="FieldViewModelDataTemplateSelector"
AudioFieldTemplate="{StaticResource TableRowAudioFieldDataTemplate}"
CheckboxFieldTemplate="{StaticResource TableRowCheckboxFieldDataTemplate}"
DatasetFieldTemplate="{StaticResource TableRowDatasetFieldDataTemplate}"
DateFieldTemplate="{StaticResource TableRowDateFieldDataTemplate}"
DateTimeFieldTemplate="{StaticResource TableRowDateTimeFieldDataTemplate}"
DropdownFieldTemplate="{StaticResource TableRowDropdownFieldDataTemplate}"
FileFieldTemplate="{StaticResource TableRowFileFieldDataTemplate}"
GpsFieldTemplate="{StaticResource TableRowGpsFieldDataTemplate}"
GridFieldTemplate="{StaticResource TableRowGridFieldDataTemplate}"
ImageFieldTemplate="{StaticResource TableRowImageFieldDataTemplate}"
LabelFieldTemplate="{StaticResource TableRowLabelFieldDataTemplate}"
MultichoiceCheckboxFieldTemplate="{StaticResource TableRowMultichoiceCheckboxFieldDataTemplate}"
RadioFieldTemplate="{StaticResource TableRowRadioFieldDataTemplate}"
RangeSliderFieldTemplate="{StaticResource TableRowRangeSliderFieldDataTemplate}"
SignatureFieldTemplate="{StaticResource TableRowSignatureFieldDataTemplate}"
SplitterFieldTemplate="{StaticResource TableRowSplitterFieldDataTemplate}"
TextFieldTemplate="{StaticResource TableRowTextFieldDataTemplate}"
TextareaFieldTemplate="{StaticResource TableRowTextareaFieldDataTemplate}"
TimeFieldTemplate="{StaticResource TableRowTimeFieldDataTemplate}"
VideoFieldTemplate="{StaticResource TableRowVideoFieldDataTemplate}"/>
</UserControl.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding Source={StaticResource FieldViewModelsSource}}"
ItemTemplateSelector="{StaticResource FieldViewModelDataTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Margin="10,0,10,0" Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
Note that the DataTemplate resources in the UserControl resources are custom UserControls that are loaded in based on objects in the ViewModel (like in my original image 1).
So, with great thanks to #LovetoCode, I managed to remedy my issue. Still trying to get over my 2-day headache and bruises from banging my head against the desk, though.
I ditched the ScrollViewer and just used my custom UserControl:
<local:TableRowUserControl Grid.Row="1" Grid.ColumnSpan="2"
Margin="66,0,66,40" HorizontalContentAlignment="Stretch" />
Then, as #LovetoCode suggested - I used a ListView instead of ItemsControl. My most sincere apologies. I didn't want to use one first time round because...
Main issue was with the ListView's default style to have hover and tap effects; which I didn't need. I tried to steer clear of disabling the hover/tap from previous experience of failing - miserably.
After a bit of Googling (other search engines are available), I found a simple solution to do this quite easily.
I managed to do it like this:
<ListView x:Name="lstFieldViewModels" ItemsSource="{Binding Source={StaticResource FieldViewModelsSource}}" SelectionMode="None"
ItemTemplateSelector="{StaticResource FieldViewModelDataTemplateSelector}" IsSwipeEnabled="False">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ContentPresenter />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Margin="10,0,10,0" Orientation="Vertical" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
Again, props to #LovetoCode. Gold star and programmer points for you :)
I want to create multiple groups of control(s) within a rectangular border. where each group will be containing control within it, surrounded by rectangular border and a header (optional) is to be placed over each child group's top-left above its border.
So, I created a class GroupLayout, each child element within this have to create its own new group. I created Header as an attached property.
Syntax making use of template is as:-
<GroupLayout Orientation = "Vertical">
<DataGrid GroupLayout.Header= "Group 1" />
<Grid GroupLayout.Header= "Group 2" />
-------So On--------
</GroupLayout>
as above given, DataGrid and Grid both should form there own two groups with vertical orientation. each child element should create its own new group.
So, I tried this as User Control:-
<Style TargetType = "GroupLayout">
<Setter.Property>
<ControlTemplate TargetType="GroupLayout">
<StackPanel>
<Border x:Name="MainParentGroupBorder">
<StackPanel>
<ContentPresenter Content = "{TemplateBinding HeaderLabel}" />
<Border x:Name="ChildGroupBorder">
<ContentPresenter Content = "{TemplateBinding Content}" />
</Border>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Property>
</Style>
In code behind I'm driving from ItemsControl.
But, this is not working as required. Now after a lot of efforts, I think I have to implement ItemTemplate in Xaml here. but I'm not able to do so to get the required result. Please help me.
Thanks,
GK Prajapati
It looks to me like you're re-inventing the wheel.
each group will be containing control within it, surrounded by rectangular border and a header (optional) is to be placed over each child group's top-left above its border
This is perfectly covered by an existing control: HeaderedContentControl. All you have to do is provide an appropriate control template for it. I suggest something like this:
<ItemsControl>
<controls:HeaderedContentControl Header="Group 1">
<DataGrid />
</controls:HeaderedContentControl>
<controls:HeaderedContentControl Header="Group 2">
<Grid />
</controls:HeaderedContentControl>
</ItemsControl>
Now, give the HeaderedContentControl the appropriate template:
<Style TargetType="controls:HeaderedContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:HeaderedContentControl">
<StackPanel>
<Border x:Name="MainParentGroupBorder">
<StackPanel>
<ContentPresenter Content="{TemplateBinding Header}" />
<Border x:Name="ChildGroupBorder">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Edit
Expanding on the above -- if you need the specific syntax of using an attached property GroupLayout.Header, then I suggest having your GroupLayout class override the ItemsControl.GetContainerForItem method. Have it return an instance of HeaderedContentControl:
protected override DependencyObject GetContainerForItemOverride()
{
return new HeaderedContentControl();
}
Now, you can use another ItemsControl override -- PrepareContainerForItemOverride -- to pass along your attached property:
protected virtual void PrepareContainerForItemOverride(DependencyObject element, Object item)
{
// get the attached property from the ItemsControl item
string header = ((FrameworkElement)item).GetValue(GroupLayout.Header) as string;
// set the container's "Header"
((HeaderedContentControl)element).Header = header;
}
Now you can use the exact XAML syntax you need:
<GroupLayout Orientation = "Vertical">
<DataGrid GroupLayout.Header= "Group 1" />
<Grid GroupLayout.Header= "Group 2" />
</GroupLayout>
In my project, I have an ItemsControl which has as its ItemsPanel template a Grid. I would like to add a single control with this grid, completely independent of the ItemsControl 'Items', which can be accessed in the code-behind. What is the best way to do this?
More detail:
I am implementing a keyframe animation timeline in WPF. I do this by having a set of 'Indicators' which is bound to an ItemsControl; the ItemsControl then positions each of these on a Grid according to their position using the Margin property:
<DataTemplate x:Key="keyIndicatorTemplate">
<Border Width="1" Height="1"
BorderBrush="Black" Background="Black" BorderThickness="0"
HorizontalAlignment="Left" VerticalAlignment="Top"
Margin="{Binding Converter={StaticResource ResourceKey=keyIndicatorMarginConv}}"
></Border>
</DataTemplate>
This Grid of indicators then automatically sizes to the key frames, and is zoomed and panned using the ItemsControl LayoutTransform property, and the encompassing ScrollViewer:
<Grid>
<ScrollViewer Name="timelineScrollViewer" Background="LightCyan" HorizontalScrollBarVisibility="Visible">
<ItemsControl Name="KeyGridPresenter" ItemTemplate="{StaticResource ResourceKey=keyIndicatorTemplate}" >
<ItemsControl.LayoutTransform>
<ScaleTransform
ScaleX="{Binding Path=ZoomX, ElementName=TimelineUserControl}"
ScaleY="{Binding Path=ZoomY, ElementName=TimelineUserControl}">
</ScaleTransform>
</ItemsControl.LayoutTransform>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid Name="KeyGrid">
</Grid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
</Grid>
I would like to add a semi-transparent 'highlighter/caret' which the user controls with the mouse, but since this design is based on the grid being an abstract 'surface' which the ItemsControl and ScrollViewer provide a 'window' to, I need to add the control in such a way that the transforms of the containers apply to it (i.e. a sibling or child of the grid)
How can I modify my ItemsTemplate, so that it instantiates a single control (border/rectangle/etc), independent of the Items?
You cannot modify the children of a panel being used as the ItemsPanelTemplate
What I usually do instead is put both the ItemsControl and the overlay control in an panel that allows its children to overlap, such as a Grid
For example,
<ScrollViewer Name="timelineScrollViewer" ...>
<Grid>
<ItemsControl Name="KeyGridPresenter" ... >
</ItemsControl>
<Grid x:Name="SomethingDrawnOnTopOfItemsControl">
...
</Grid>
</Grid>
</ScrollViewer>