Currently my CustomControl adjust his element color affectionate of the status received from the PLC. The CustomControl haves two dependency property: Statebrush and InstanceAdresFFU. As you can see, the InstanceAdres has the same starting adres as the StateBrush adres.
MainView1.xaml
<cc:CustomControl1 x:Name="FFU_2" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="4" Margin="96,243,0,0" Width="44" Height="30" BorderThickness="1"
StateBrush="{vw:VariableBinding VariableName=MCS1.Cleanroom.SIM_Cleanroom.SIM_FFUControl2.Observers.oStatus, Converter={StaticResource ValueToStateBrushConverter}, States={StaticResource BrushListState}, StateMode=Value}"
InstanceAdresFFU="MCS1.Cleanroom.SIM_Cleanroom.SIM_FFUControl2"/>
Therefore I want to get rid of the StateBrush binding in the MainView.
<cc:CustomControl1 x:Name="FFU_2" HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="4" Margin="96,243,0,0" Width="44" Height="30" BorderThickness="1"
InstanceAdresFFU="MCS1.Cleanroom.SIM_Cleanroom.SIM_FFUControl2"/>
And in the code behind, something like this:
StateBrush= this.InstanceAdresFFU + ".Observers.oStatus", Converter={StaticResource ValueToStateBrushConverter}, States={StaticResource BrushListState}, StateMode=Value}"
So my question is, is it possible (and how) to add a default variable to a depency property in the backhand, with its associated Converter={StaticResource ValueToStateBrushConverter}, States={StaticResource BrushListState}, StateMode=Value}"
CustomControl1.cs
namespace HMI.CustomControl
{
public class CustomControl1 : System.Windows.Controls.Button
{
public Brush StateBrush
{
get { return (Brush)GetValue(StateBrushProperty); }
set { SetValue(StateBrushProperty, value); }
}
// Using a DependencyProperty as the backing store for StateBrush. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StateBrushProperty =
DependencyProperty.Register(nameof(StateBrush), typeof(Brush), typeof(CustomControl1), new PropertyMetadata(default(Brush)));
public string InstanceAdresFFU
{
get { return (string)GetValue(InstanceAdresFFUyProperty); }
set { SetValue(InstanceAdresFFUyProperty, value); }
}
// Using a DependencyProperty as the backing store for InstanceAdresFFU. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InstanceAdresFFUyProperty =
DependencyProperty.Register("InstanceAdresFFU", typeof(string), typeof(CustomControl1), new PropertyMetadata(""));
}
}
Generic.xaml : Frontend of object
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cc="clr-namespace:HMI.CustomControl" xmlns:vw="http://inosoft.com/visiwin7">
<Style TargetType="{x:Type cc:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type cc:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="19*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<vw:Label Margin="5" Text="{TemplateBinding LabelText}" Height="40" Focusable="False"/>
<Rectangle Fill="{TemplateBinding StateBrush}" Width="40" Height="24" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="1,1,0,0" Grid.ColumnSpan="6"/>
<Line Stroke="#FF000000" Height="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="1,4,0,0" Width="40" Y1="0" Y2="0.114682539682633" X2="39.5" StrokeThickness=".5" Grid.ColumnSpan="6"/>
<Line Stroke="#FF000000" Height="1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="1,1,-2,0" Width="43" Y1="0" Y2="0.114682539682633" X2="39.5" Grid.ColumnSpan="6"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Due to the lack of valuable information I will show you some very general examples to give you an idea.
You must register a property change callback and handle the InstanceAdresFFU value and the ValueBinding markup extension.
The example assumes that the referenced resources are defined in the App.xaml resources.
If VariableBinding extends BindingBase
public class CustomControl1 : Button
{
public string InstanceAdresFFU
{
get => (string)GetValue(InstanceAdresFFUyProperty);
set => SetValue(InstanceAdresFFUyProperty, value);
}
// Using a DependencyProperty as the backing store for InstanceAdresFFU. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InstanceAdresFFUyProperty = DependencyProperty.Register(
"InstanceAdresFFU",
typeof(string),
typeof(CustomControl1),
new PropertyMetadata(default(string), OnInstanceAdresFFUChanged));
private static void OnInstanceAdresFFUChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as CustomControl1).OnInstanceAdresFFUChanged();
public MultiSelectComboBox() => this.Loaded += OnLoaded;
private void OnLoaded(object sender, RoutedEventArgs e)
=> OnInstanceAdresFFUChanged();
protected virtual void OnInstanceAdresFFUChanged()
{
if (string.IsNullOrWhiteSpace(this.InstanceAdresFFU))
{
return;
}
IValueConverter converter = (IValueConverter)Application.Current.Resources["ValueToStateBrushConverter"];
object states = Application.Current.Resources["BrushListState"];
var binding = new VariableBinding
{
VariableName = $"{this.InstanceAdresFFU}.Observers.oStatus",
Converter = converter,
States = states,
StateMode = "Value"
}
SetBinding(StateBrushProperty, binding);
}
}
If VariableBinding does not extend BindingBase
public class CustomControl1 : Button
{
public string InstanceAdresFFU
{
get => (string)GetValue(InstanceAdresFFUyProperty);
set => SetValue(InstanceAdresFFUyProperty, value);
}
// Using a DependencyProperty as the backing store for InstanceAdresFFU. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InstanceAdresFFUyProperty = DependencyProperty.Register(
"InstanceAdresFFU",
typeof(string),
typeof(CustomControl1),
new PropertyMetadata(default(string), OnInstanceAdresFFUChanged));
private static void OnInstanceAdresFFUChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
=> (d as CustomControl1).OnInstanceAdresFFUChanged();
public MultiSelectComboBox() => this.Loaded += OnLoaded;
private void OnLoaded(object sender, RoutedEventArgs e)
=> OnInstanceAdresFFUChanged();
protected virtual void OnInstanceAdresFFUChanged()
{
if (string.IsNullOrWhiteSpace(this.InstanceAdresFFU))
{
return;
}
IValueConverter converter = (IValueConverter)Application.Current.Resources["ValueToStateBrushConverter"];BrushListState
object states = Application.Current.Resources["BrushListState"];
var markupExtension = new VariableBinding
{
VariableName = $"{this.InstanceAdresFFU}.Observers.oStatus",
Converter = converter,
States = states,
StateMode = "Value"
}
this.StateBrush = markupExtension.ProvideValue(new ServiceProviders());
}
}
Related
I'm building a Custom Control (or Templated, if you mind), but I can't figure out how to bind the event (the Click) of a button inside the custom control to the Click event of the Custom Control itself.
I have searched on the internet, but some solution was only for WPF (including classes not available in the UWP platform), some was for Visual Basic, some other wasn't exactly my case and so on...
Here is the code, that works perfectly so far, for best clearance (please note, I have changed the project's and namespace's name to hide it, putting instead "SomeClass"):
The custom control, IconButton.cs:
public sealed class IconButton : Control
{
public IconButton()
{
this.DefaultStyleKey = typeof(IconButton);
}
public Boolean IconButtonIsLabelVisible
{
get { return (Boolean)GetValue(IconButtonIsLabelVisibleProperty); }
set { SetValue(IconButtonIsLabelVisibleProperty, value); }
}
public static readonly DependencyProperty IconButtonIsLabelVisibleProperty =
DependencyProperty.Register("IconButtonIsLabelVisible", typeof(Boolean), typeof(IconButton), new PropertyMetadata(true));
public String IconButtonLabel
{
get { return (String)GetValue(IconButtonLabelProperty); }
set { SetValue(IconButtonLabelProperty, value); }
}
public static readonly DependencyProperty IconButtonLabelProperty =
DependencyProperty.Register("IconButtonLabel", typeof(String), typeof(IconButton), new PropertyMetadata("Content"));
public Double IconButtonLabelMargin
{
get { return (Double)GetValue(IconButtonLabelMarginProperty); }
set { SetValue(IconButtonLabelMarginProperty, value); }
}
public static readonly DependencyProperty IconButtonLabelMarginProperty =
DependencyProperty.Register("IconButtonLabelMargin", typeof(Double), typeof(IconButton), new PropertyMetadata(10));
public Style IconButtonStyle
{
get { return (Style)GetValue(IconButtonStyleProperty); }
set { SetValue(IconButtonStyleProperty, value); }
}
public static readonly DependencyProperty IconButtonStyleProperty =
DependencyProperty.Register("IconButtonStyle", typeof(Style), typeof(IconButton), new PropertyMetadata(null));
public IconElement IconButtonIcon
{
get { return (IconElement)GetValue(IconButtonIconProperty); }
set { SetValue(IconButtonIconProperty, value); }
}
public static readonly DependencyProperty IconButtonIconProperty =
DependencyProperty.Register("IconButtonIcon", typeof(IconElement), typeof(IconButton), new PropertyMetadata(0));
}
The generic xaml template file, Generic.xaml:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SomeClass.Controls">
<Style TargetType="local:IconButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:IconButton">
<Button x:Name="ClickButton" Style="{TemplateBinding IconButtonStyle}" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" Command="{TemplateBinding Command}" CommandParameter="{TemplateBinding CommandParameter}">
<Grid Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ContentPresenter x:Name="Content"
Content="{TemplateBinding IconButtonIcon}"
Foreground="{TemplateBinding Foreground}" VerticalAlignment="Center"/>
<Grid Grid.Column="1" Width="{TemplateBinding IconButtonLabelMargin}"/>
<TextBlock Grid.Column="2" Text="{TemplateBinding IconButtonLabel}" Foreground="{TemplateBinding Foreground}" VerticalAlignment="Center"/>
</Grid>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the MainPage.xaml, where I would like to use the IconButton:
<Page
x:Class="SomeClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SomeClass"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:testControls="using:SomeClass.Controls"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<testControls:IconButton x:Name="TestButton" Click"?" IconButtonLabelMargin="5" HorizontalAlignment="Center" Foreground="Aqua" VerticalAlignment="Center" Background="Transparent" >
<testControls:IconButton.IconButtonIcon>
<SymbolIcon Symbol="Preview"/>
</testControls:IconButton.IconButtonIcon>
</testControls:IconButton>
</Grid>
So, given this code, I would like to bind in some way the Click event of the ClickButton in
the xaml template of the IconButton to the default Click event of the
IconButton control itself, so that it can be easily used in the mainpage by
simply specifying the Click event.
Thank you for you kindness and your attention.
Best regards.
Doing this requires overriding the OnApplyTemplate method in your control, finding the named template part in your control, and raising the event on your wrapper.
Inside your custom control:
ButtonBase clickButtonPart = null;
public const string ClickButtonTemplatePartName = "ClickButton";
public event EventHandler Click;
protected override void OnApplyTemplate()
{
// In case the template changes, you want to stop listening to the
// old button's Click event.
if (clickButtonPart != null)
{
clickButtonPart.Click -= ClickForwarder;
clickButtonPart = null;
}
// Find the template child with the special name. It can be any kind
// of ButtonBase in this example.
clickButtonPart = GetTemplateChild(ClickButtonTemplatePartName) as ButtonBase;
// Add a handler to its Click event that simply forwards it on to our
// Click event.
if (clickButtonPart != null)
{
clickButtonPart.Click += ClickForwarder;
}
}
private void ClickForwarder(object sender, Windows.UI.Xaml.RoutedEventArgs e)
{
Click?.Invoke(this, null);
}
In my control having content presenter which inside the canvas panel. I have arranged((Align as center, left and right in screen point) the content in canvas based on content actual size. Initial content size loaded properly and when dynamically change content, it actual size always return 0. Due to this, can't align content in screen position. Can you please advise me how to get content size in dynamic case like below scenario
<Page.Resources>
<!--control style-->
<Style TargetType="local:CustomLabel">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:CustomLabel">
<Grid>
<Canvas>
<Ellipse Height="10" Width="10" Canvas.Left="300" Canvas.Top="300" Fill="Red" />
</Canvas>
<Canvas>
<ContentPresenter Margin="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelMargin}" Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
</Canvas>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<Grid>
<local:CustomLabel x:Name="label">
<local:CustomLabel.Content>
<Grid>
<TextBlock Text="Initial Label" />
</Grid>
</local:CustomLabel.Content>
</local:CustomLabel>
<Button Content="Change text" VerticalAlignment="Bottom" Height="75" Click="Button_Click" />
</Grid>
public MainPage()
{
this.InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var grid = new Grid();
grid.Children.Add(new TextBlock() { Text = "Change" });
this.label.Content = grid;
}
// control implementation
public class CustomLabel : Control
{
bool loadTime = false;
public CustomLabel()
{
this.SizeChanged += CustomLabel_SizeChanged;
}
private void CustomLabel_SizeChanged(object sender, SizeChangedEventArgs e)
{
loadTime = true;
CalculateContentPosition();
}
private void CalculateContentPosition()
{
if (loadTime)
{
var content = Content as FrameworkElement;
if (content != null)
{
var left = content.ActualWidth / 2;
var top = content.ActualHeight / 2;
this.LabelMargin = new Thickness(300 - left, 300 - top, 0, 0);
}
}
}
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
// Using a DependencyProperty as the backing store for Content. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(CustomLabel), new PropertyMetadata(null,OnContentChanged));
private static void OnContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
(d as CustomLabel).CalculateContentPosition();
}
public Thickness LabelMargin
{
get { return (Thickness)GetValue(LabelMarginProperty); }
set { SetValue(LabelMarginProperty, value); }
}
// Using a DependencyProperty as the backing store for LabelMargin. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LabelMarginProperty =
DependencyProperty.Register("LabelMargin", typeof(Thickness), typeof(CustomLabel), new PropertyMetadata(new Thickness(1)));
}
The issue was because you access its size too early, it even has not been rendered on XAML page. You might ask why the Grid has size on XAML page at first time. It's because the parent panel has done this operation(similar Measure(size)) when it layouts its child elements.
So, to solve your question, you could register the LayoutUpdated event of your 'CustomLabel'. Then, in its event handler, you could call CalculateContentPosition() method, instead of calling in OnContentChanged method.
public CustomLabel()
{
this.SizeChanged += CustomLabel_SizeChanged;
this.LayoutUpdated += CustomLabel_LayoutUpdated;
}
private void CustomLabel_LayoutUpdated(object sender, object e)
{
CalculateContentPosition();
}
I have a stripped down WPF example of a problem I am having in a much larger project. I have a user control called "UserControl1". The data context is set to self so I have dependency properties defined in the code-behind.
In the control I have an ItemsControl. In the ItemsSource I have a CompositeCollection that contains a CollectionContainer and a Line. The Line is there just to prove to myself that I am drawing.
I also have an object called "GraphPen" that contains a PathGeometry dependency property. The CollectionContainer of the user control contains an ObservableCollection of these GraphPens.
Now, I have a "MainWindow" to test the user control. In the MainWindow I have a DispatchTimer and in the Tick event of that timer, I add LineSegments to a PathFigure which has been added to the Figures collection of the PathGeometry of the single instance of the GraphPen.
I expect to see a diagonal line being drawn in parallel to the existing red line, but nothing shows up. If I put a break point at the end of the Tick event handler, I can examine the user control and drill down and see that the line segments do exist. For some reason they are not being rendered. I suspect I have done something wrong in the binding.
I will supply the code below.
GraphPen.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
namespace WpfExampleControlLibrary
{
public class GraphPen : DependencyObject
{
#region Constructor
public GraphPen()
{
PenGeometry = new PathGeometry();
}
#endregion Constructor
#region Dependency Properties
// Line Color
public static PropertyMetadata PenLineColorPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenLineColorProperty
= DependencyProperty.Register(
"PenLineColor",
typeof(Brush),
typeof(GraphPen),
PenLineColorPropertyMetadata);
public Brush PenLineColor
{
get { return (Brush)GetValue(PenLineColorProperty); }
set { SetValue(PenLineColorProperty, value); }
}
// Line Thickness
public static PropertyMetadata PenLineThicknessPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenLineThicknessProperty
= DependencyProperty.Register(
"PenLineThickness",
typeof(Int32),
typeof(GraphPen),
PenLineThicknessPropertyMetadata);
public Int32 PenLineThickness
{
get { return (Int32)GetValue(PenLineThicknessProperty); }
set { SetValue(PenLineThicknessProperty, value); }
}
// Pen Geometry
public static PropertyMetadata PenGeometryMetadata = new PropertyMetadata(null);
public static DependencyProperty PenGeometryProperty
= DependencyProperty.Register(
"PenGeometry",
typeof(PathGeometry),
typeof(UserControl1),
PenGeometryMetadata);
public PathGeometry PenGeometry
{
get { return (PathGeometry)GetValue(PenGeometryProperty); }
set { SetValue(PenGeometryProperty, value); }
}
#endregion Dependency Properties
}
}
UserControl1.xaml
<UserControl Name="ExampleControl"
x:Class="WpfExampleControlLibrary.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfExampleControlLibrary"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:GraphPen}">
<Path Stroke="{Binding Path=PenLineColor}"
StrokeThickness="{Binding Path=PenLineThickness}"
Data="{Binding Path=Geometry}">
</Path>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Column="0" Grid.Row="0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Aquamarine">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5"/>
</Canvas.LayoutTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{
Binding Source={RelativeSource Self},
Path=GraphPens,
Mode=OneWay}"/>
<Line X1="10" Y1="0" X2="200" Y2="180" Stroke="DarkRed" StrokeThickness="2"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
<TextBox x:Name="debug" Grid.Column="0" Grid.Row="1" Text="{Binding Path=DebugText}"/>
</Grid>
</UserControl>
UserControl1.xaml.cs
namespace WpfExampleControlLibrary
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
GraphPens = new ObservableCollection<GraphPen>();
}
#region Dependency Properties
// Pens
public static PropertyMetadata GraphPenMetadata = new PropertyMetadata(null);
public static DependencyProperty GraphPensProperty
= DependencyProperty.Register(
"GraphPens",
typeof(ObservableCollection<GraphPen>),
typeof(UserControl1),
GraphPenMetadata);
public ObservableCollection<GraphPen> GraphPens
{
get { return (ObservableCollection<GraphPen>)GetValue(GraphPensProperty); }
set { SetValue(GraphPensProperty, value); }
}
// Debug Text
public static PropertyMetadata DebugTextMetadata = new PropertyMetadata(null);
public static DependencyProperty DebugTextProperty
= DependencyProperty.Register(
"DebugText",
typeof(string),
typeof(UserControl1),
DebugTextMetadata);
public string DebugText
{
get { return (string)GetValue(DebugTextProperty); }
set { SetValue(DebugTextProperty, value); }
}
#endregion Dependency Properties
}
}
MainWindow.xaml
<Window x:Class="POC_WPF_UserControlExample.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:Exmpl="clr-namespace:WpfExampleControlLibrary;assembly=WpfExampleControlLibrary"
xmlns:local="clr-namespace:POC_WPF_UserControlExample"
mc:Ignorable="d"
Title="MainWindow" Height="550" Width="550">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition />
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Exmpl:UserControl1 Grid.Column="1" Grid.Row="1" x:Name="myExample"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace POC_WPF_UserControlExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private DispatcherTimer _timer = null;
private GraphPen _graphPen0 = null;
private Int32 _pos = 0;
private PathFigure _pathFigure = null;
public MainWindow()
{
InitializeComponent();
_graphPen0 = new GraphPen();
_graphPen0.PenLineColor = Brushes.DarkGoldenrod;
_graphPen0.PenLineThickness = 2;
myExample.GraphPens.Add(_graphPen0);
_timer = new DispatcherTimer();
_timer.Tick += Timer_Tick;
_timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
_timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
_pos++;
Point penPoint0 = new Point(_pos, _pos + 20);
if (_graphPen0.PenGeometry.Figures.Count == 0)
{
_pathFigure = new PathFigure();
_graphPen0.PenGeometry.Figures.Add(_pathFigure);
_pathFigure.StartPoint = penPoint0;
}
else
{
LineSegment segment = new LineSegment(penPoint0, false);
_pathFigure.Segments.Add(segment);
}
myExample.DebugText = _pos.ToString();
}
}
}
Screen Shot
I did not need INotifyPropertyChanged, and I did not need to recreate PenGeometry. I'm sorry I wasted your time with those ideas.
I've got your code drawing... something. A line that grows. I don't know if it's drawing exactly what you want, but you can figure out that part now that you can see what it is drawing.
First, minor copy/paste error in GraphPen.cs:
public static DependencyProperty PenGeometryProperty
= DependencyProperty.Register(
"PenGeometry",
typeof(PathGeometry),
typeof(UserControl1),
PenGeometryMetadata);
Owner type parameter needs to be GraphPen, not UserControl1:
typeof(PathGeometry),
typeof(GraphPen),
PenGeometryMetadata);
Second: Binding in UserControl1. Your binding to Self isn't going to work because Self, in that case, is the CollectionContainer you're binding on. Usually you'd use a source of RelativeSource={RelativeSource AncestorType=UserControl}, but CollectionContainer is not in the visual tree so that doesn't work (real intuitive, huh?). Instead we use a BindingProxy (source to follow):
<UserControl.Resources>
<!-- ... stuff ... -->
<local:BindingProxy
x:Key="UserControlBindingProxy"
Data="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"
/>
</UserControl.Resources>
And for the collection container...
<CollectionContainer
Collection="{
Binding
Source={StaticResource UserControlBindingProxy},
Path=Data.GraphPens,
Mode=OneWay}"
/>
Notice we're binding to Data.GraphPens; Data is the target of the proxy.
Also, we need an ItemTemplate for the ItemsControl, because it doesn't know how to display a GraphPen:
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:GraphPen">
<Border >
<Path
Data="{Binding PenGeometry}"
StrokeThickness="{Binding PenLineThickness}"
Stroke="{Binding PenLineColor, PresentationTraceSources.TraceLevel=None}"
/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
Note PresentationTraceSources.TraceLevel=None. Change None to High and it'll give you a lot of debugging info about the Binding in the VS Output pane. I added that when I was trying to figure out why I was setting PenLineColor to Brushes.Black in the constructor, but it kept coming out DarkGoldenrod at runtime. Can you say duhhh? Duhhh!
Now the binding proxy. What that does is you take any random object you want to use as a DataContext, bind it to Data on a BindingProxy instance defined as a resource, and you've got that DataContext available via a resource that you can get to with a StaticResource. If you're someplace where you can't get to something via the visual tree with RelativeSource, it's an option that you can rely on.
BindingProxy.cs
using System.Windows;
namespace WpfExampleControlLibrary
{
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}
And finally, in MainWindow, you need to pass true for isStroked on the LineSegment instances:
LineSegment segment = new LineSegment(penPoint0, true);
Otherwise they're not drawn.
So now it's back in your lap, in warm goldenrod and soothing aquamarine. Ave, imperator, and all that.
Edit by original author!
Thank you Ed for all your effort.
I can't believe I missed the UserControl1 -> GraphPen
The BindingProxy will be very handy
The TraceLevel will also be handy, I had not used that before
I also corrected the DebugText binding and now that works
I never even noticed that!
In the DataTemplate why did we need to wrap the Path in a Border?
We don't. Before I got the Path showing, I added that to have something in the template that was guaranteed to be visible . It had a green border originally. I removed those attributes, but forgot to remove the Border itself.
If you look in my Timer_Tick you will note that now all I have to update is adding new segments. Hopefully, this will help performance. Your opinion?
No idea. I would actually put that segment adding code in GraphPen as AddSegment(Point pt) and AddSegment(float x, float y) => AddSegment(new Point(x,y)); overloads. I have a great allergy to putting logic in event handlers. The most I'll do is toss an if or a try/catch around a non-handler method that does the real work. Then I'd write AddSegment(Point pt) both ways and benchmark one against the other.
I will add my code for completeness:
UserControl1.xaml
<UserControl Name="ExampleControl"
x:Class="WpfExampleControlLibrary.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfExampleControlLibrary"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<local:BindingProxy
x:Key="UserControlBindingProxy"
Data="{Binding RelativeSource={RelativeSource AncestorType=UserControl}}"/>
<DataTemplate DataType="{x:Type local:GraphPen}">
<Border>
<Path
Data="{Binding PenGeometry}"
StrokeThickness="{Binding PenLineThickness}"
Stroke="{Binding
PenLineColor,
PresentationTraceSources.TraceLevel=None}"
/>
</Border>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<ItemsControl Grid.Column="0" Grid.Row="0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Aquamarine">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="-1" CenterX=".5" CenterY=".5"/>
</Canvas.LayoutTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<CollectionContainer
Collection="{Binding
Source={StaticResource UserControlBindingProxy},
Path=Data.GraphPens,
Mode=OneWay}"/>
<Line X1="10" Y1="0" X2="200" Y2="180" Stroke="DarkRed" StrokeThickness="2"/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
<TextBox
x:Name="debug"
Grid.Column="0" Grid.Row="1"
Text="{Binding
Source={StaticResource UserControlBindingProxy},
Path=Data.DebugText,
Mode=OneWay}"/>
</Grid>
</UserControl>
UserControl1.xaml.cs
namespace WpfExampleControlLibrary
{
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
#region Constructor
public UserControl1()
{
InitializeComponent();
GraphPens = new ObservableCollection<GraphPen>();
}
#endregion Constructor
#region Public Methods
#endregion Public Methods
#region Dependency Properties
// Pens
public static PropertyMetadata GraphPenMetadata = new PropertyMetadata(null);
public static DependencyProperty GraphPensProperty
= DependencyProperty.Register(
"GraphPens",
typeof(ObservableCollection<GraphPen>),
typeof(UserControl1),
GraphPenMetadata);
public ObservableCollection<GraphPen> GraphPens
{
get { return (ObservableCollection<GraphPen>)GetValue(GraphPensProperty); }
set { SetValue(GraphPensProperty, value); }
}
// Debug Text
public static PropertyMetadata DebugTextMetadata = new PropertyMetadata(null);
public static DependencyProperty DebugTextProperty
= DependencyProperty.Register(
"DebugText",
typeof(string),
typeof(UserControl1),
DebugTextMetadata);
public string DebugText
{
get { return (string)GetValue(DebugTextProperty); }
set { SetValue(DebugTextProperty, value); }
}
#endregion Dependency Properties
}
}
GraphPen.cs
namespace WpfExampleControlLibrary
{
public class GraphPen : DependencyObject
{
#region Constructor
public GraphPen()
{
PenGeometry = new PathGeometry();
}
#endregion Constructor
#region Dependency Properties
// Line Color
public static PropertyMetadata PenLineColorPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenLineColorProperty
= DependencyProperty.Register(
"PenLineColor",
typeof(Brush),
typeof(GraphPen),
PenLineColorPropertyMetadata);
public Brush PenLineColor
{
get { return (Brush)GetValue(PenLineColorProperty); }
set { SetValue(PenLineColorProperty, value); }
}
// Line Thickness
public static PropertyMetadata PenLineThicknessPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenLineThicknessProperty
= DependencyProperty.Register(
"PenLineThickness",
typeof(Int32),
typeof(GraphPen),
PenLineThicknessPropertyMetadata);
public Int32 PenLineThickness
{
get { return (Int32)GetValue(PenLineThicknessProperty); }
set { SetValue(PenLineThicknessProperty, value); }
}
// Pen Geometry
public static PropertyMetadata PenGeometryMetadata = new PropertyMetadata(null);
public static DependencyProperty PenGeometryProperty
= DependencyProperty.Register(
"PenGeometry",
typeof(PathGeometry),
typeof(GraphPen),
PenGeometryMetadata);
public PathGeometry PenGeometry
{
get { return (PathGeometry)GetValue(PenGeometryProperty); }
set { SetValue(PenGeometryProperty, value); }
}
#endregion Dependency Properties
}
}
BindingProxy.cs
namespace WpfExampleControlLibrary
{
public class BindingProxy : Freezable
{
#region Override Freezable Abstract Parts
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion Override Freezable Abstract Parts
#region Dependency Properties
// Using a DependencyProperty as the backing store for Data.
// This enables animation, styling, binding, etc...
public static PropertyMetadata DataMetadata = new PropertyMetadata(null);
public static readonly DependencyProperty DataProperty
= DependencyProperty.Register(
"Data",
typeof(object),
typeof(BindingProxy),
DataMetadata);
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
#endregion Dependency Properties
}
}
MainWindow.xaml
<Window x:Class="POC_WPF_UserControlExample.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:Exmpl="clr-namespace:WpfExampleControlLibrary;assembly=WpfExampleControlLibrary"
xmlns:local="clr-namespace:POC_WPF_UserControlExample"
mc:Ignorable="d"
Title="MainWindow" Height="550" Width="550">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition />
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="100" />
<RowDefinition />
<RowDefinition Height="100" />
</Grid.RowDefinitions>
<Exmpl:UserControl1 Grid.Column="1" Grid.Row="1" x:Name="myExample"/>
</Grid>
</Window>
MainWindow.xaml.cs
namespace POC_WPF_UserControlExample
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private DispatcherTimer _timer = null;
private GraphPen _graphPen0 = null;
private Int32 _pos = 0;
private PathFigure _pathFigure0 = null;
private bool _firstTime = true;
public MainWindow()
{
InitializeComponent();
_pathFigure0 = new PathFigure();
_graphPen0 = new GraphPen();
_graphPen0.PenLineColor = Brushes.DarkGoldenrod;
_graphPen0.PenLineThickness = 2;
_graphPen0.PenGeometry = new PathGeometry();
_graphPen0.PenGeometry.Figures.Add(_pathFigure0);
myExample.GraphPens.Add(_graphPen0);
_timer = new DispatcherTimer();
_timer.Tick += Timer_Tick;
_timer.Interval = new TimeSpan(0, 0, 0, 0, 100);
_timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
_pos++;
Point penPoint0 = new Point(_pos, _pos + 20);
if (_firstTime)
{
myExample.GraphPens[0].PenGeometry.Figures[0].StartPoint = penPoint0;
_firstTime = false;
}
else
{
LineSegment segment = new LineSegment(penPoint0, true);
myExample.GraphPens[0].PenGeometry.Figures[0].Segments.Add(segment);
}
myExample.DebugText = _pos.ToString();
}
}
}
I have the following user control:
The Xaml:
<UserControl x:Class="ScreenRecorder.TimePicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Height="27" Width="176">
<Grid>
<StackPanel Orientation="Horizontal">
<TextBox Width="150" Height="25" Text="{Binding Time}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<StackPanel Orientation="Vertical">
<Button Width="25" Height="12.5" HorizontalAlignment="Left" VerticalAlignment="Top" Click="btnKeyUp_Clicked">
<Image Source="up.png" Height="10" Width="10" VerticalAlignment="Top"/>
</Button>
<Button Width="25" Height="12.5" HorizontalAlignment="Left" VerticalAlignment="Top" Click="btnKeyDown_Clicked">
<Image Source="down.png" Height="10" Width="10" VerticalAlignment="Top"/>
</Button>
</StackPanel>
</StackPanel>
</Grid>
The Code:
public partial class TimePicker : UserControl, INotifyPropertyChanged
{
public TimePicker()
{
InitializeComponent();
this.DataContext = this;
//Time = m_time;
}
public static DependencyProperty TimeProperty = DependencyProperty.Register(
"Time", typeof(string), typeof(TimePicker));
//private string m_time = DateTime.Now.ToString();
public string Time
{
get { return (string)GetValue(TimeProperty); }
set
{
SetValue(TimeProperty, value);
NotifyPropertyChanged("Time");
}
}
private void btnKeyUp_Clicked(object sender, RoutedEventArgs e)
{
DateTime curTime = Convert.ToDateTime(Time);
curTime += new TimeSpan(0, 0, 1);
Time = curTime.ToString();
}
private void btnKeyDown_Clicked(object sender, RoutedEventArgs e)
{
DateTime curTime = Convert.ToDateTime(Time);
curTime -= new TimeSpan(0, 0, 1);
Time = curTime.ToString();
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
And I have another user control that uses this user control as follow:
<StackPanel>
<Label Content="Begin Record Time" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="5"/>
<local:TimePicker Time="{Binding StartRecordTime}"/>
</StackPanel>
StartRecordTime looks like this:
public string StartRecordTime
{
get { return m_startRecord; }
set
{
m_startRecord = value;
NotifyPropertyChanged("StartRecordTime");
}
}
I want to change the StartRecordTime according to the Time Property and vice versa, but only the Time property is changing.
Thank you.
Try this:
<local:TimePicker Time="{Binding Path=StartRecordTime, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
Your binding won't work as you've set the DataContext of TimePicker to TimePicker itself. You'll notice a binding error in the Output window:
BindingExpression path error: 'StartRecordTime' property not found on 'object' ''TimePicker' (Name='')'. BindingExpression:Path=StartRecordTime; DataItem='TimePicker' (Name=''); target element is 'TimePicker' (Name=''); target property is 'Time' (type 'String')
I'd suggest to have a more 'sane' experience you remove DataContext = this from the TimePicker constructor and set the Grid's DataContext to the TimePicker by Element Name. Add a name attribute to the UserControl element:
<UserControl x:Name="Root" ...
And set the DataContext of the Grid:
<Grid DataContext="{Binding ElementName=Root}">
This will be inherited by all child elements. You will also need to change your binding to TwoWay, either explicitly:
<local:TimePicker Time="{Binding Path=StartRecordTime, Mode=TwoWay}" />
Or by setting this as the default in the DependencyProperty registration:
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
"Time", typeof(string), typeof(TimePicker),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
I'd also note that there's no reason for TimePicker to implement INotifyPropertyChanged. I suggest you remove this.
I have the following class:
public class LooklessControl : Control
{
public List<int> IntList { get; private set; }
public int CurrentInt { get; private set; }
private int _index = 0;
static LooklessControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(LooklessControl), new FrameworkPropertyMetadata(typeof(LooklessControl)));
}
public LooklessControl()
{
IntList = new List<int>();
for (int i = 0; i < 10; i++)
{
IntList.Add(i);
}
CurrentInt = IntList[_index];
}
public static readonly RoutedCommand NextItemCommand =
new RoutedCommand("NextItemCommand", typeof(LooklessControl));
private void ExecutedNextItemCommand(object sender, ExecutedRoutedEventArgs e)
{
NextItemHandler();
}
private void CanExecuteNextItemCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public static readonly RoutedCommand PrevItemCommand =
new RoutedCommand("PrevItemCommand", typeof(LooklessControl));
private void ExecutedPrevItemCommand(ExecutedRoutedEventArgs e)
{
PrevItemHandler();
}
private void CanExecutePrevItemCommand(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = true;
}
public static readonly RoutedEvent NextItemEvent =
EventManager.RegisterRoutedEvent("NextItemEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(LooklessControl));
public event RoutedEventHandler NextItem
{
add { AddHandler(NextItemEvent, value); }
remove { RemoveHandler(NextItemEvent, value); }
}
private void RaiseNextItemEvent()
{
RoutedEventArgs args = new RoutedEventArgs(LooklessControl.NextItemEvent);
RaiseEvent(args);
}
public static readonly RoutedEvent PrevItemEvent =
EventManager.RegisterRoutedEvent("PrevItemEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(LooklessControl));
public event RoutedEventHandler PrevItem
{
add { AddHandler(PrevItemEvent, value); }
remove { RemoveHandler(PrevItemEvent, value); }
}
private void RaisePrevItemEvent()
{
RoutedEventArgs args = new RoutedEventArgs(LooklessControl.PrevItemEvent);
RaiseEvent(args);
}
private void NextItemHandler()
{
_index++;
if (_index == IntList.Count)
{
_index = 0;
}
CurrentInt = IntList[_index];
RaiseNextItemEvent();
}
private void PrevItemHandler()
{
_index--;
if (_index == 0)
{
_index = IntList.Count - 1;
}
CurrentInt = IntList[_index];
RaisePrevItemEvent();
}
}
The class has a default style, in Generic.xaml, that looks like this:
<Style x:Key="{x:Type local:LooklessControl}" TargetType="{x:Type local:LooklessControl}">
<Setter Property="Height" Value="200"/>
<Setter Property="Width" Value="90"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:LooklessControl}">
<Border BorderBrush="Black" BorderThickness="1" Padding="2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Rectangle Grid.Row="0" Fill="LightGray"/>
<Rectangle Grid.Row="1" Fill="Gainsboro"/>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="10"/>
</Grid.ColumnDefinitions>
<Path Grid.Column="0" x:Name="pathLeftArrow" Data="M0,0.5 L1,1 1,0Z" Width="6" Height="14" Stretch="Fill"
HorizontalAlignment="Center" Fill="SlateBlue"/>
<TextBlock Grid.Column="1" Name="textBlock"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=CurrentInt}"
HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Junction" FontSize="13"/>
<Path Grid.Column="2" x:Name="pathRightArrow" Data="M0,0 L1,0.5 0,1Z" Width="6" Height="14" Stretch="Fill"
HorizontalAlignment="Center" Fill="SlateBlue"/>
</Grid>
<ListBox Grid.Row="1" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Background="Transparent"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IntList}"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How do I make it so that when the user clicks on pathLeftArrow it fires LooklessControl.PrevItemCommand, or or they click on pathRightArrow and it fires LooklessControl.NextItemCommand, or they click on an item in the ListBox and LooklessControl is notified of the newly selected item?
In other words, without adding x:Class to the top of Generic.xaml and thus creating a code-behind file for it, which I assume you wouldn't want to do, how do you handle events for elements in your xaml that don't have a Command property (which is just about everything other than a Button)?
Should LooklessControl have it's own XAML file (much like what you get when you create a new UserControl) associated with it that Generic.xaml just pulls in as a MergedDictionar as its default template? Or is there some other acknowledged way to do what I'm trying to do?
To answer your last question: NO. The lookless control shouldn't require any known XAML. That is what lookless means.
You have a couple of options here, but I would recommend wrapping your elements in Buttons with a basically empty control template:
<ControlTemplate x:Key="contentOnlyButton" TargetType="{x:Type Button}">
<ContentPresenter />
</ControlTemplate>
...
<Button Grid.Column="0" Template="{StaticResource contentOnlyButton}"
Command="{x:Static local:LooklessControl.PrevItemCommand}">
<Path x:Name="pathLeftArrow" Data="M0,0.5 L1,1 1,0Z" Width="6" Height="14"
Stretch="Fill" HorizontalAlignment="Center" Fill="SlateBlue"/>
</Button>
Your other option (and I would say this is probably not what you should do for executing commands on clicks, but may be applicable in other circumstances), would be to look for the named part in your template in OnApplyTemplate, and wire up the events.
public override void OnApplyTemplate()
{
var prevElement = this.GetTemplateChild("PART_PathLeftArrow") as UIElement;
if (prevElement != null)
prevElement.MouseDown += (o, e) => PrevItemHandler();
...
}
One thing to note with doing this is that the Template isn't required to define the parts you are looking for, so you need to gracefully check for that circumstance. Throwing NullReferenceExceptions here will make restyling your control a royal pain for designers / developers who accidentally delete a required element. You will also want to follow the standard practice of naming your required elements with a PART_ syntax, and decorating your class with TemplatePart attributes.
[TemplatePart(Name = "PART_PathLeftArrow", Type = typeof(UIElement))]
[TemplatePart(Name = "PART_PathRightArrow", Type = typeof(UIElement))]
...
public class LooklessControl : Control
Edit: In order for the Button's to respond to the clicks, you need to setup CommandBindings to your functions that you had already defined. You would do this as a class command binding like so:
static LooklessControl()
{
CommandManager.RegisterClassCommandBinding(
typeof(LooklessControl),
new CommandBinding(NextItemCommand, ExecutedNextItemCommand, CanExecuteNextItemCommand));
CommandManager.RegisterClassCommandBinding(
typeof(LooklessControl),
new CommandBinding(PrevItemCommand, ExecutedPrevItemCommand, CanExecutePrevItemCommand));
}
The reason to do a class command binding is that if you add it to your control's CommandBindings collection, somebody using your control could inadvertently remove them. Also remember to update your command handling methods to have static semantics.