WPF Cannot Capture Mouse events in Nested CustomControls - wpf

I have the following Control template in my app.xaml: (it is in a template setter in a style, so there is no key)
<ControlTemplate TargetType="{x:Type vm:State}">
<Grid Width="100" Height="60" >
<Border Margin="4"
IsHitTestVisible="{Binding ElementName=Radio_Edit02, Path=IsChecked}"
Padding="4"
BorderBrush="White"
BorderThickness="2"
CornerRadius="5">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{Binding ItemName, RelativeSource={RelativeSource TemplatedParent}}"/>
</Border>
<Grid Margin="3" Background="Transparent">
<local:Anchor Width="6" Height="6" HorizontalAlignment="Center" VerticalAlignment="Top" Background="Red" Visibility="{Binding IsChecked, ElementName=Radio_Edit03, Converter={StaticResource bool2vizibility}}"/>
<local:Anchor Width="6" Height="6" HorizontalAlignment="Right" VerticalAlignment="Center" Background="Red" Visibility="{Binding IsChecked, ElementName=Radio_Edit03, Converter={StaticResource bool2vizibility}}"/>
<local:Anchor Width="6" Height="6" HorizontalAlignment="Left" VerticalAlignment="Center" Background="Red" Visibility="{Binding IsChecked, ElementName=Radio_Edit03, Converter={StaticResource bool2vizibility}}"/>
<local:Anchor Width="6" Height="6" HorizontalAlignment="Center" VerticalAlignment="Bottom" Background="Red" Visibility="{Binding IsChecked, ElementName=Radio_Edit03, Converter={StaticResource bool2vizibility}}"/>
</Grid>
</Grid>
</ControlTemplate>
State derives from thumb
public class State : Thumb, INotifyPropertyChanged
Anchor derives from UserControl
public class Anchor : UserControl, INotifyPropertyChanged
I want to capture a click of the State at one point in the app and of the Anchor in another point of the app.
I want to control when by the two radiobuttons, Radio_Edit02 & Radio_Edit03
when Radio_Edit02 is selected I want to capture the click on the border
when Radio_Edit03 is selected I want to capture the click on the anchor.
I keep getting the click event on the State and cannot get a click on the Anchor.
I tried adding IsHitTestVisible="{Binding ElementName=Radio_Edit03, Path=IsChecked}" to the anchor
but no luck
I tried the following assignement of event handlers in Anchor but neither worked
public Anchor() //constructor
{
//this//this.MouseLeftButtonUp += onAnchorSelect;
//this.PreviewMouseLeftButtonUp += onAnchorSelect;
}
signature for onAnchorSelect is
public void onAnchorSelect(object sender, MouseButtonEventArgs e)
How can I capture a click on the Anchor so that e.Source is the anchor element
thanks

Related

ContextMenu - Strange behavior when using ItemContainerStyle

I'm having a strange issue with my ContextMenu. The ItemsSource is a List<Layer> Layers;, where the Layer class overrides the ToString() to return the Name property.
If I don't use any ItemContainerStyle for the context menu, it all works fine - it takes the Layer object and displays the ToString() of that object, like it should. When I add the ItemContainerStyle, it shows an empty string.
Here's the XAML:
<Style x:Key="myItemControlTemplate" TargetType="{x:Type MenuItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type MenuItem}">
<Border SnapsToDevicePixels="True" Height="32" Width="200" Background="White">
<Grid VerticalAlignment="Center" Height="Auto" Width="Auto" Background="{x:Null}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="textBlock" HorizontalAlignment="Left" TextWrapping="Wrap" Text="{TemplateBinding Header}" VerticalAlignment="Top" Foreground="#FF3B3D52"
Grid.Column="1" Margin="6,0,0,0"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Button ContextMenuService.IsEnabled="False">
<Button.ContextMenu>
<ContextMenu FontFamily="Global Sans Serif" Height="Auto" Width="200" Padding="0,6" VerticalOffset="5" BorderThickness="0"
HasDropShadow="True" ItemContainerStyle="{StaticResource myItemControlTemplate}">
</ContextMenu>
</Button.ContextMenu>
</Button>
And here's how I fire it:
private void btn_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
ContextMenu ctm = btn.ContextMenu;
ctm.ItemsSource = Layers;
ctm.PlacementTarget = btn;
ctm.Placement = PlacementMode.Bottom;
ctm.IsOpen = true;
}
Could it be that for some reason this binding gets busted somehow?
Text="{TemplateBinding Header}"
BTW, if I change the layers list to be a List<string> and just feed it the names of the layers, it works correctly with the ItemContainerStyle.
What am I missing?
The issue is the TemplateBinding. This is a less powerful but optimized variant of a relative source binding to a templated parent, but it comes at the expense of several limitations, like not supporting two-way binding. Although not officially stated in the documentation, it seems that this binding does not support conversion of the underlying type to string, as ToString is never called in this case.
Replace the TemplateBinding with a binding to the templated parent using relative source.
<TextBlock x:Name="textBlock"
HorizontalAlignment="Left" TextWrapping="Wrap"
Text="{Binding Header, RelativeSource={RelativeSource TemplatedParent}}"
VerticalAlignment="Top" Foreground="#FF3B3D52"
Grid.Column="1" Margin="6,0,0,0"/>

Make attached properties work inside DataTemplate

I got an ItemsControl which uses a Canvas as ItemsPanel and its items are rendered to different WPF shapes depending on the bound type, basically like this:
<ItemsControl ItemsSource="{Binding PreviewShapes}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl.Resources>
<DataTemplate DataType="{x:Type local:UiPreviewLineViewModel}">
<Line X1="{Binding Start.X}" Y1="{Binding Start.Y}"
X2="{Binding End.X}" Y2="{Binding End.Y}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Line" ToolTip="{Binding Text}">
</Line>
</DataTemplate>
<DataTemplate DataType="{x:Type local:UiPreviewEllipsisViewModel}">
<Ellipse Canvas.Left="{Binding UpperLeft.X" Canvas.Top="{Binding UpperLeft.Y}"
Width="{Binding Width}" Height="{Binding Height}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Ellipse" ToolTip="{Binding Text}">
</Ellipse>
</DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="SketchCanvas" ClipToBounds="False">
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
So I basically add objects to PreviewShapes of the viewmodel and depending on the type they are rendered to WPF Lines or Ellipses. That basically works but the attached properties Canvas.Left and Canvas.Top are ignored, even when using static values.
Also VS or ReSharper notifies me that the attached property has no effect in the current context.
How can I position the Ellipse on the canvas without using the attached properties? Or what other solution would be appropiate?
Unfortunately nobody felt like posting an answer.
First, Clemens links are helpful. The items will be inside a ContentPresenter which is the reason why setting Canvas.Left/Top on the Ellipsis does not work.
Solution 1
By adding a style to the item container the bindings for the position can be set there:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding UpperLeft.X}" />
<Setter Property="Canvas.Top" Value="{Binding UpperLeft.Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
This works but the DataTemplate placing <Line> will produce binding warnings because that view model does not have a property UpperLeft. Nevertheless it works for the ellipsis and the lines are placed by their X1, Y1, X2 and Y2 values.
Solution 2
If you would like to use a more fine grained control approach you can set the attached Canvas properties to the ContentPresenter by proxing them with a custom behaviour / attached property. Let's name it CanvasPointProxyBehavior, you could use it for the Ellipse like this:
<DataTemplate DataType="{x:Type local:UiPreviewEllipsisViewModel}">
<Ellipse behaviors:CanvasPointProxyBehavior.Point="{Binding UpperLeft}"
Width="{Binding Width}" Height="{Binding Height}"
StrokeThickness="0.75" Stroke="{Binding Brush}" x:Name="Ellipse" ToolTip="{Binding Text}">
</Ellipse>
</DataTemplate>
The Line will not need it. The code for this attached property might look like this:
public class CanvasPointProxyBehavior
{
public static readonly DependencyProperty PointProperty = DependencyProperty.RegisterAttached("Point", typeof(Point), typeof(CanvasPointProxyBehavior), new UIPropertyMetadata(null, PointChangedCallback));
public static void SetPoint(DependencyObject depObj, Point point)
{
depObj.SetValue(PointProperty, point);
}
public static Point GetPoint(DependencyObject depObj)
{
return depObj.GetValue(PointProperty) as Point;
}
private static void PointChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
UIElement uiElement = (dependencyObject as UIElement);
if (uiElement == null) return;
UIElement elementToBePositioned = uiElement;
var visualParent = VisualTreeHelper.GetParent(uiElement);
if (visualParent is ContentPresenter)
{
elementToBePositioned = visualParent as ContentPresenter;
}
var point = e.NewValue as Point;
if (point != null)
{
Canvas.SetLeft(elementToBePositioned, point.X);
Canvas.SetTop(elementToBePositioned, point.Y);
}
}
}
Hoping someone will find one or both solution useful.
Please note that I encountered the same ReSharper warning message as #ZoolWay, but in my case it was within a data grid, where I wanted the button on the right to be right-aligned instead of left-aligned:
Attached property setting "Grid.Column" has no effect in current
context and can be removed.
Here is the code where I had the warning, on the Button Grid.Column="2":
<Border VerticalAlignment="Center" Style="{DynamicResource InspectionsCustomDataGridHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label Name="InspectionsDataGridTitle" Background="White" Content="{Binding InspectionsCollectionView.View.Count, ConverterParameter={x:Static resx:Resources.InspectionsDataGridTitle}, Converter={StaticResource DataGridHeaderCountConverter}}" Style="{DynamicResource InspectionsCustomDataGridHeaderLabel}" MouseDown="InspectionsDataGridTitle_MouseDown" />
<!-- more labels, etc... -->
<Button Grid.Column="2" Width="30" Height="30" Margin="0,0,10,0" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" Command="{Binding CreatePDFCommand,Mode=OneWay}" >
<StackPanel Orientation="Horizontal" Background="Transparent">
<Image Width="30" Height="30" Source="/Inspections;component/Icons/pdf_btn.png" />
</StackPanel>
</Button>
</StackPanel>
</Grid>
</Border>
This is how I fixed the warning, where I moved the button below the first StackPanel:
<Border VerticalAlignment="Center" Style="{DynamicResource InspectionsCustomDataGridHeader}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" VerticalAlignment="Top">
<Label Name="InspectionsDataGridTitle" Background="White" Content="{Binding InspectionsCollectionView.View.Count, ConverterParameter={x:Static resx:Resources.InspectionsDataGridTitle}, Converter={StaticResource DataGridHeaderCountConverter}}" Style="{DynamicResource InspectionsCustomDataGridHeaderLabel}" MouseDown="InspectionsDataGridTitle_MouseDown" />
<!-- more labels, etc... -->
</StackPanel>
<Button Grid.Column="2" Width="30" Height="30" Margin="0,0,10,0" Background="Transparent" BorderThickness="0" HorizontalAlignment="Right" Command="{Binding CreatePDFCommand,Mode=OneWay}" >
<StackPanel Orientation="Horizontal" Background="Transparent">
<Image Width="30" Height="30" Source="/Inspections;component/Icons/pdf_btn.png" />
</StackPanel>
</Button>
</Grid>
</Border>
HTH

Accessing elements inside ControlTemplate xaml by c#

I have a wpf application there i have ControlTemplate in the App.xaml. This ControlTemplate contains Grid and Then Canvas.
<Application.Resources>
<ControlTemplate x:Key="PinTemplate" TargetType="m:Pushpin">
<Grid x:Name="grid2" HorizontalAlignment="left" VerticalAlignment="Center" >
<Canvas x:Name="ContentPopup" Visibility="{TemplateBinding Visibility}">
<StackPanel x:Name="stackPanel1" Canvas.Left="0" Canvas.Top="-20" Visibility="{TemplateBinding Visibility}">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}"
Margin="0" TextBlock.FontFamily="Segoe UI" TextBlock.FontWeight="Bold" TextBlock.FontSize="10" TextBlock.Foreground="Blue">
</ContentPresenter>
</StackPanel>
</Canvas>
<Canvas>
<Ellipse Name="Ellips12" Fill="AliceBlue" Opacity="0.7" Stroke="Red" StrokeThickness="2" Height="25" Stretch="Fill" Canvas.Top="5" Width="25" />
<TextBlock Text="{TemplateBinding ContentStringFormat }" TextBlock.Foreground="Blue" FontSize="13" TextBlock.TextAlignment="Right" Margin="10,7,10,0">
</TextBlock>
</Canvas>
</Grid>
</ControlTemplate>
</Application.Resources>
I need help to access the Grid and the Canvas inside this ControlTemplate from the mainWindow.xaml.cs.
I want to change the attribut "Visibility" for it when PropertyChanged or when ViewChangeEnd event.
that what i have tried but doesnt work.
ControlTemplate ct = Application.Current.Resources["PinTemplate"] as ControlTemplate;
Grid gr = ct.Resources["grid2"] as Grid;
gr.Visibility = Visibility.Collapsed;
and even this
ControlTemplate ct = Application.Current.Resources["PinTemplate"] as ControlTemplate;
Grid gr = ct.FindName("grid2", this) as Grid;
ControlTemplate.FindName returns the Item only after the template is applied on the control. If you are doing this in UserControl you could get it by overriding OnApplyTemplate. Or if you are outside the UserControl you could use Control.Loaded Event for the same :)
hope it helped :)

Configurable HeaderTemplate

I have about 12 Expanders that need to have a custom HeaderTemplate. The HeaderTemplate has a textblock to display the header text, along with a button. The button has a custom control template so that I can display the button as a Path within a VisualBrush.
<Expander VerticalAlignment="Top"
Header="Fields"
IsExpanded="True">
<Expander.HeaderTemplate>
<DataTemplate>
<DockPanel LastChildFill="False">
<TextBlock VerticalAlignment="Center"
DockPanel.Dock="Left"
FontSize="14"
Foreground="{DynamicResource IdealForegroundColorBrush}"
Text="{Binding}"/>
<Button DockPanel.Dock="Right" Margin="0 0 10 0"
Command="{Binding RelativeSource={RelativeSource AncestorType=Expander}, Path=DataContext.AddFieldCommand}">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid>
<Rectangle Fill="Transparent" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Content="{TemplateBinding Content}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</ControlTemplate>
</Button.Template>
<Rectangle Width="20" Height="20" Fill="{DynamicResource EditIconBrush}" />
</Button>
</DockPanel>
</DataTemplate>
</Expander.HeaderTemplate>
I would like to re-use this template across all 12 Expanders, but need to specify what icon and Command the button within the template will use. How would I go about doing that? Can this be achieved by breaking the data template up in to a series of templates? What I want to do is move the template into a static resource
<Expander VerticalAlignment="Top"
Header="Fields"
IsExpanded="True"
HeaderTemplate="{StaticResource IconHeader}">
I'm just not sure how to provide the template what Command and Icon resource it should use. Would a Dependency property make sense here?
Thanks.
I believe that there are at least to ways to achieve this. First is to inherit from Expander, extend it with your Dependency properties and bind to them inside DataTemplate.
Another way to do this is to create some attached properties, and bind to them inside DataTemplate as below:
public class TemplateConfig
{
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof (ICommand), typeof (TemplateConfig), new PropertyMetadata(default(ICommand)));
public static void SetCommand(DependencyObject element, ICommand value)
{
element.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject element)
{
return (ICommand) element.GetValue(CommandProperty);
}
public static readonly DependencyProperty IconProperty = DependencyProperty.RegisterAttached("Icon", typeof (VisualBrush), typeof (TemplateConfig), new PropertyMetadata(default(VisualBrush)));
public static void SetIcon(DependencyObject element, VisualBrush value)
{
element.SetValue(IconProperty, value);
}
public static VisualBrush GetIcon(DependencyObject element)
{
return (VisualBrush) element.GetValue(IconProperty);
}
}
Header template:
<DataTemplate x:Key="ExpanderHeaderTemplate">
<DockPanel LastChildFill="False">
<TextBlock VerticalAlignment="Center"
DockPanel.Dock="Left"
FontSize="14"
Text="{Binding}" />
<Button Margin="0 0 10 0"
Command="{Binding Path=(test:TemplateConfig.Command),
RelativeSource={RelativeSource AncestorType=Expander}}"
DockPanel.Dock="Right">
<Button.Template>
<ControlTemplate TargetType="Button">
<Grid>
<Rectangle Fill="Transparent" />
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Button.Template>
<Rectangle Width="20"
Height="20"
Fill="{Binding Path=(test:TemplateConfig.Icon),
RelativeSource={RelativeSource AncestorType=Expander}}" />
</Button>
</DockPanel>
</DataTemplate>
and finally expander:
<Expander VerticalAlignment="Top"
Header="Fields"
HeaderTemplate="{StaticResource ExpanderHeaderTemplate}"
IsExpanded="True"
test:TemplateConfig.Command="{Binding SomeCommand}"
test:TemplateConfig.Icon="{StaticResource VisualBrush}" />
Hope this helps.

WPF DataTemplate previewmouseleftbuttonup not working

I have Listbox with ListItemTemplate. There are two commands I want attach with each list item.
1) PreviewMouseLeftButtonDown : I use this even when for drag and drop functionality. user press button event gets fired and I came to know how many items user has selected for dragging.
2) PreviewMouseLeftButtonUp: I want to use this when user release mouse from list item. (But issue is this even never gets fired. It seems like 1st event taking control of both.
Here is my code. Pls help.
<DataTemplate x:Key="ListItemTemplate">
<Grid Margin="0" Width="58" Height="58" x:Name="OuterGrid">
<Border x:Name="OuterBorder" BorderBrush="{DynamicResource ContentToGreyedOutBrush}" BorderThickness="0" Margin="0" Background="Transparent" Grid.Column="0" Grid.Row="0"
ClipToBounds="True" CornerRadius="0">
<Border x:Name="InnerBorder" BorderBrush="Transparent" BorderThickness="1" Margin="0" Background="Transparent" CornerRadius="0">
<Grid>
<Image Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Source="{Binding Path=FileName,Converter={StaticResource FileNameImageConverter}}"
Width="50" Height="50">
</Image>
<ToggleButton x:Name="zoomButton" Grid.Column="0" Grid.Row="0" Margin="0" HorizontalAlignment="Right" HorizontalContentAlignment="Right" VerticalAlignment="Bottom"
VerticalContentAlignment="Bottom" Background="Transparent" Cursor="Hand" Template="{StaticResource ZoomTemplate}" Width="20" Height="20" Visibility="Collapsed">
</ToggleButton>
</Grid>
</Border>
</Border>
<ac:CommandBehaviorCollection.Behaviors>
<ac:BehaviorBinding Event="PreviewMouseLeftButtonDown" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}},Path=DataContext.DragItemSelectedCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"/>
<ac:BehaviorBinding Event="PreviewMouseLeftButtonUp" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}},Path=DataContext.MouseUPCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"/>
</ac:CommandBehaviorCollection.Behaviors>
</Grid>
</DataTemplate>
It looks like you're trying to do dragging of the item to a different location outside the original bounds. In this case the up event would be firing on the new control over which the mouse is located when it is released, not the control that you started dragging. To make this work you need to make sure to capture the mouse when starting a drag operation.

Resources