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();
}
}
}
Related
I have solution with one wpf application assembly MyApp and one .NET class library assembly CutomUC. The CustomUC class library is supposed to hold all of my custom user controls which are used from wpf application.
I have written simple ImageButton UserControl. Whenever I set some dependency property of my custom control from wpf application, designer wont render, and I get the following warning (or error, it is shown as blue underline in xaml):
The member "[member name]" is not recognized or is not accessible
However, application builds and runs just fine.
Here is my custom UserControl, from CustomUC assembly:
public partial class ImageButton : UserControl
{
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ImageButton), new UIPropertyMetadata(OnArrangementChanged));
public static readonly DependencyProperty ImageProperty =
DependencyProperty.Register("Image", typeof(ImageSource), typeof(ImageButton), new UIPropertyMetadata(null));
public static readonly DependencyProperty ImageMarginProperty =
DependencyProperty.Register("ImageMargin", typeof(string), typeof(ImageButton), new UIPropertyMetadata("0, 0, 0, 0"));
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(ImageButton), new UIPropertyMetadata(new PropertyChangedCallback(OnArrangementChanged)));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public ImageSource Image
{
get { return (ImageSource)GetValue(ImageProperty); }
set { SetValue(ImageProperty, value); }
}
public string ImageMargin
{
get { return (string)GetValue(ImageMarginProperty); }
set { SetValue(ImageMarginProperty, value); }
}
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
private static void OnArrangementChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var myUserControl = sender as ImageButton;
myUserControl.RearangeElements();
}
public ImageButton()
{
InitializeComponent();
RearangeElements();
}
private void RearangeElements()
{
if (String.IsNullOrEmpty(Text))
{
Grid.SetRow(ButtonImage, 0);
Grid.SetColumn(ButtonImage, 0);
Grid.SetRowSpan(ButtonImage, 2);
Grid.SetColumnSpan(ButtonImage, 2);
}
else if (this.Orientation == Orientation.Horizontal)
{
Grid.SetColumnSpan(ButtonImage, 1);
Grid.SetColumnSpan(ButtonTextBlock, 1);
Grid.SetRowSpan(ButtonTextBlock, 2);
Grid.SetRowSpan(ButtonImage, 2);
Grid.SetColumn(ButtonImage, 0);
Grid.SetColumn(ButtonTextBlock, 1);
Grid.SetRow(ButtonImage, 0);
Grid.SetRow(ButtonTextBlock, 0);
}
else if (this.Orientation == Orientation.Vertical)
{
Grid.SetColumnSpan(ButtonTextBlock, 2);
Grid.SetColumnSpan(ButtonImage, 2);
Grid.SetRowSpan(ButtonTextBlock, 1);
Grid.SetRowSpan(ButtonImage, 1);
Grid.SetRow(ButtonImage, 0);
Grid.SetRow(ButtonTextBlock, 1);
Grid.SetColumn(ButtonImage, 0);
Grid.SetColumn(ButtonTextBlock, 0);
}
}
}
Here is ImageButton.xaml:
<UserControl x:Class="CustomUC.UserControls.ImageButton"
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:CustomUC.UserControls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Name="ImageButton"
Background="Transparent"
BorderBrush="Transparent"
BorderThickness="0">
<Button Name="ButtonElement"
Height="{Binding ElementName=ImageButton, Path=Height}"
Width="{Binding ElementName=ImageButton, Path=Width}">
<Button.Content>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<Image Name="ButtonImage" Source="{Binding ElementName=ImageButton, Path=Image}" Margin="{Binding ElementName=ImageButton, Path=ImageMargin}"/>
<TextBlock Name="ButtonTextBlock" Text="{Binding ElementName=ImageButton, Path=Text}" TextWrapping="Wrap" TextAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</Button.Content>
</Button>
</UserControl>
And here is MainWindow.xaml from MyApp assembly:
<Window x:Class="MyApp.View.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:resx="clr-namespace:MyApp.localization"
xmlns:local="clr-namespace:MyApp.View"
xmlns:customuc="clr-namespace:CustomUC.UserControls;assembly=CustomUC"
mc:Ignorable="d"
Title="MainWindow" Height="720" Width="1280">
<Grid>
<customuc:ImageButton Width="100" Height="100" Text="Hello!" Image="/MyApp;component/Assets/352456 - drive file insert.ico" Orientation="Vertical" Margin="840,103,332,486"/>
</Grid>
</Window>
Text, Image and Orientation all show the same warning. I want to be able to access dependency properties as if the control was in the same assembly (I want designer to render correctly, and autocomplete feature). Is that possible, am I missing something?
Turns out everything was fine. Once I got back to the computer, turned it and VS on everything was working as expected. My best guess would be that VS didn't load something that was needed yesterday.
If you are having the same problem, try restarting your VS.
I'm trying to bind a reusable button in a carusel, what I want to achieve is a add lets say 6 buttons each button will have a command that according to button name will navigate to the proper page.
I can do that by doing this:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.MenuList, Mode=OneWay}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="2"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<Button Command="{x:Bind ViewModel.NavigateToPage1, Mode=OneWay}"
Content="{x:Bind ViewModel.Name, Mode=OneWay}"/>
</toolkitcontrols:Carousel>
If I do that, I'll be adding 5 more buttons and i'll have to write properties for each button.
So instead I want to use a UserControl and just write something like this:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.MenuList, Mode=OneWay}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="2"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate x:DataType="data:ButtonInfo">
<usercontrolvm:NavigationMenuButtonTemplate NavigateToPageCommand="{Binding NavigateToPageCommand}"/>
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
But I've failed doing it, I found out some tutorial but all as I understand will make me write this like of code:
<usercontrolvm:NavigationMenuButtonTemplate NavigateToPageCommand="{Binding NavigateToPageCommand}"/>
like 6 times, and i dont know how it will take x:DataType of DataTemplate for my list of properties.
This is my UserControl.xaml.cs
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public ButtonInfo ButtonInfo => (DataContext as ButtonInfo);
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
Loaded += NavigationMenuButtonTemplate_Loaded;
}
private void NavigationMenuButtonTemplate_Loaded(object sender, RoutedEventArgs e)
{
Bindings.Update();
}
public DelegateCommand NavigateToPageCommand
{
get { return (DelegateCommand)GetValue(NavigateToPageCommandProperty); }
set { SetValue(NavigateToPageCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for NavigateToPageCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigateToPageCommandProperty =
DependencyProperty.Register("NavigateToPageCommand",
typeof(DelegateCommand),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
}
this is my ButtonInfo.cs
public class ButtonInfo
{
public string Symbol { get; set; }
public string FontFamily { get; set; }
public string MenuName { get; set; }
public string BenefitKind { get; set; }
public string Status { get; set; }
}
and this is my UserControl.xaml
<Button x:Name="NavigationMenuTemplate"
Width="300"
Height="300"
Command="{Binding NavigateToPageCommand, ElementName=root, Mode=OneWay}">
<Grid x:Name="ButtonLayout">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="NavigationMenuIconTextBlock"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="2"
FontFamily="{x:Bind ButtonInfo.FontFamily, Mode=OneWay, FallbackValue='Webdings'}"
Text="{x:Bind ButtonInfo.Symbol, Mode=OneWay, FallbackValue=''}"
FontSize="150"
Foreground="Black"
VerticalAlignment="Center"
HorizontalAlignment="Center"/>
<TextBlock x:Name="NavigationMenuButtonNameTextBlock"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
Text="{x:Bind ButtonInfo.MenuName, Mode=OneWay, FallbackValue='CALCULADORA JORNADAS EXTRAORDINARIAS'}"
FontSize="12"
Foreground="Black"
HorizontalAlignment="Center"/>
<TextBlock x:Name="NavigationMenuButtonBenefitKindTextBlock"
Grid.Row="2"
Grid.Column="0"
Text="{x:Bind ButtonInfo.BenefitKind, Mode=OneWay, FallbackValue='Subscripción'}"
FontSize="10"
Foreground="Black"
HorizontalAlignment="Left"/>
<TextBlock x:Name="NavigationMenuButtonStatusTextBlock"
Grid.Row="2"
Grid.Column="1"
Text="{x:Bind ButtonInfo.Status, Mode=OneWay, FallbackValue='Vigente'}"
FontSize="10"
Foreground="Black"
HorizontalAlignment="Right"/>
</Grid>
</Button>
can somebody help me and point me in the right direction please.
what am I missing?
The ItemTemplate approach in your question is actually on the right track.
In the end, your XAML would look something similar to the following(only a few properties are included but you get the idea) -
<toolkitcontrols:Carousel ItemsSource="{x:Bind ButtonInfoCollection}">
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate x:DataType="local:ButtonInfo">
<local:NavigationMenuButton NavigateToPageCommand="{Binding DataContext.NavigateToPageCommand, ElementName=MyPageName}"
NavigateToPageCommandParameter="{x:Bind PageType}"
MenuName="{x:Bind MenuName}"
SymbolPath="{x:Bind Symbol}" />
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
With the structure above in mind, you just need to expose these properties as dependency properties in your NavigationMenuButton user control. See below as a simple example -
NavigationMenuButton XAML
<UserControl x:Class="DesignTest.NavigationMenuButton">
<!--If any of the properties can be updated, change the binding Mode to OneWay-->
<Button Command="{x:Bind NavigateToPageCommand, Mode=OneWay}" CommandParameter="{x:Bind NavigateToPageCommandParameter}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image x:Name="SymbolImage" Stretch="UniformToFill" />
<TextBlock Text="{x:Bind MenuName, FallbackValue='JORNADAS EXTRAORDINARIAS', TargetNullValue='JORNADAS EXTRAORDINARIAS'}" Grid.Column="1" />
</Grid>
</Button>
</UserControl>
NavigationMenuButton Code-behind
public sealed partial class NavigationMenuButton : UserControl
{
public NavigationMenuButton()
{
InitializeComponent();
}
public ICommand NavigateToPageCommand
{
get => (ICommand)GetValue(NavigateToPageCommandProperty);
set => SetValue(NavigateToPageCommandProperty, value);
}
public static readonly DependencyProperty NavigateToPageCommandProperty = DependencyProperty.Register(
"NavigateToPageCommand", typeof(ICommand), typeof(NavigationMenuButton), new PropertyMetadata(null));
public object NavigateToPageCommandParameter
{
get => GetValue(NavigateToPageCommandParameterProperty);
set => SetValue(NavigateToPageCommandParameterProperty, value);
}
public static readonly DependencyProperty NavigateToPageCommandParameterProperty = DependencyProperty.Register(
"NavigateToPageCommandParameter", typeof(object), typeof(NavigationMenuButton), new PropertyMetadata(null));
public string MenuName
{
get => (string)GetValue(MenuNameProperty);
set => SetValue(MenuNameProperty, value);
}
public static readonly DependencyProperty MenuNameProperty = DependencyProperty.Register(
"MenuName", typeof(string), typeof(NavigationMenuButton), new PropertyMetadata(null));
public string SymbolPath
{
get => (string)GetValue(SymbolPathProperty);
set => SetValue(SymbolPathProperty, value);
}
public static readonly DependencyProperty SymbolPathProperty = DependencyProperty.Register(
"SymbolPath", typeof(string), typeof(NavigationMenuButton), new PropertyMetadata(null, (s, e) =>
{
// We don't do the x:Bind for this property in XAML because the Image control's Source property
// doesn't accept a string but a BitmapImage, so one workaround is to do the conversion here.
var self = (NavigationMenuButton)s;
var image = self.SymbolImage;
var symbolPath = (string)e.NewValue;
image.Source = new BitmapImage(new Uri(self.BaseUri, symbolPath ?? "/Assets/default_path"));
}));
}
Note you will need to include a PageType property in your ButtonInfo class for navigation purpose.
public Type PageType { get; set; }
I personally don't like having a navigation command defined at the item level (i.e in the ButtonInfo class), instead, I use an ElementName binding in the Carousel's data template to search up a level and bind to the NavigateToPageCommand defined in the page's DataContext, which is the page's ViewModel.
This means that this ViewModel will have both ButtonInfoCollection and NavigateToPageCommand defined like below -
public ObservableCollection<ButtonInfo> ButtonInfoCollection { get; } = new ObservableCollection<ButtonInfo>
{
new ButtonInfo { MenuName = "New Menu", PageType = typeof(BlankPage1), Symbol = "/Assets/StoreLogo.png" }
};
public DelegateCommand<Type> NavigateToPageCommand { get; } = new DelegateCommand<Type>(type =>
App.Frame.Navigate(type));
I hope this all makes sense. Good luck!
Ok, Firstable to Dilmah its always posible to build a reusable usercontrol within any itemTemplate. And I'll show you how right now and right here.
I have come up with two solution the first solution its inspire in what I was looking for after reading a lot about databing {x:Bind} and {Binding} markup, I was able to learn how to create a reusable UserControlTemplate
SOLUTION No.1
First I'll show you how to create a Navigation menu with a carusel control, which can be found at nuget package Microsoft.Toolkit.Uwp.UI.Control
so this is my code now on my MainMenuPage reference to carusel control:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemsSource="{x:Bind ViewModel.NavMenuButtonVMs}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="0"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<toolkitcontrols:Carousel.ItemTemplate>
<DataTemplate>
<usercontrolvm:NavigationMenuButtonTemplate/>
</DataTemplate>
</toolkitcontrols:Carousel.ItemTemplate>
</toolkitcontrols:Carousel>
this important part of this code is at ItemSource property which is x:Bind to my NavMenuButtonVms ObservableCollection, and my Usercontrol which is wrapped withing Carousel.ItemTemplate and DataTemplate tags which will allows us to be able to reuse our code and create a N number of controls within our list.
the next is my ViewModel for my MainMenuPage:
public class MainMenuPageViewModel : Mvvm.ViewModelBase
{
ObservableCollection<NavigationMenuButtonTemplateViewModel> _NavMenuButtonVMs = default(ObservableCollection<NavigationMenuButtonTemplateViewModel>);
public MainMenuPageViewModel()
{
Shell.HamburgerMenu.IsFullScreen = false;
NavMenuButtonVMs = GetNavMenuButtonInfo();
}
public string Title => GetLocalizeString("MainMenuPageViewModelTitle");
public ObservableCollection<NavigationMenuButtonTemplateViewModel> NavMenuButtonVMs
{
get { return _NavMenuButtonVMs; }
private set { Set(ref _NavMenuButtonVMs, value); }
}
public override Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> state)
{
NavigationService.ClearHistory();
return base.OnNavigatedToAsync(parameter, mode, state);
}
}
As you can see I initialize my ObservableCollection within my constructor.
The method GetNavMenuButton() is a static class in a Helpers Namespace but i'll show you the code so you can have an idea of how to seed the list also you can notice that I'm not calling the static class that because i'm using C# 6.0 syntax where you can call directly static methods within your class.
you can add a using statement for static classes like this one:
using static Ceneam.Helpers.NavigationMenuButtonViewModelHelper;
this statement allows you to use a static method like this:
GetNavMenuButtonInfo();
instead of this:
NavigationMenuButtonViewModelHelper.GetNavMenuButtonInfo();
I explained this in case you dont understand my code.
then I'll create my usercontrol which I'll show you the xaml, xaml.cs and also the viewmodel.
pay attention to the binding markup at usercontrol since you'll have to quit using x:Bind within a reusable usercontrol.
This is my NavigationMenuButtonTemplate.xaml
<UserControl
x:Class="Ceneam.UserControlViews.NavigationMenuButtonTemplate"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Ceneam.UserControlViews"
xmlns:vm="using:Ceneam.ViewModels"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="400"
d:DesignWidth="400">
<Grid VerticalAlignment="Center"
HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Button x:Name="NavigationMenuTemplate"
Command="{Binding NavigateToPageCommand, Mode=OneWay}">
<Grid x:Name="ButtonLayout">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image x:Name="NavigationMenuIconImage"
Source="{Binding ButtonInfo.Symbol, Mode=OneWay, FallbackValue='ms-appx:///Assets/AssetsMainMenuPage/OverTimeMoneyWhite256x256.png'}"
PointerEntered="NavigationMenuIconImage_PointerEntered"/>
<TextBlock x:Name="NavigationMenuButtonNameTextBlock"
Text="{Binding ButtonInfo.MenuName, Mode=OneWay, FallbackValue='JORNADAS EXTRAORDINARIAS'}"/>
<TextBlock x:Name="NavigationMenuButtonBenefitKindTextBlock"
Text="{Binding ButtonInfo.BenefitKind, Mode=OneWay, FallbackValue='Subscripción'}"/>
<TextBlock x:Name="NavigationMenuButtonStatusTextBlock"
Text="{Binding ButtonInfo.Status, Mode=OneWay, FallbackValue='Vigente'}"/>
</Grid>
</Button>
</Grid>
as you can see I'm using binding markup only and the reason is because I use a viewmodel with a parameter that right i had to create a dependency on my usercontrol:
public class NavigationMenuButtonTemplateViewModel : Mvvm.ViewModelBase
{
ButtonInfo _ButtonInfo = default(ButtonInfo);
public NavigationMenuButtonTemplateViewModel() { }
public NavigationMenuButtonTemplateViewModel(ButtonInfo buttonInfo)
{
ButtonInfo = new ButtonInfo
{
BenefitKind = buttonInfo.BenefitKind,
Status = buttonInfo.Status,
MenuName = buttonInfo.MenuName,
Symbol = buttonInfo.Symbol
};
}
public ButtonInfo ButtonInfo
{
get { return _ButtonInfo; }
set { Set(ref _ButtonInfo, value); }
}
public DelegateCommand NavigateToPageCommand => new DelegateCommand(async () => { await ExecuteNavigateToPageCommand(); });
private async Task ExecuteNavigateToPageCommand()
{
var message = new MessageDialog("Test");
await message.ShowAsync();
}
}
since you create a contructor with a parameter in the viewmodel I wasnt able to create a strongly type bind with that constructor that is the main reason that made me leave behind x:bind markup for my usercontrol that means that you cant use x:bind methods on events. you will have to use styling methods within the xaml.cs file of your usercontrol.
If you declare in you xaml something like this:
<UserControl.DataContext>
<vm:usercontrol x:Name=ViewModel/>
<UserControl.DataContext>
it will always trigger your parameterless constructor getting rid of your init values of even worse getting NullReferenceExceptions, also you could use DataContext for DesignTime Data but it has to be declare at the beginning of your file and I wont center on it here.
and finally at my static class is where I create my UC (usercontrol) with a parameter in them this is my static class:
public static class NavigationMenuButtonViewModelHelper
{
public static ObservableCollection<NavigationMenuButtonTemplateViewModel> GetNavMenuButtonInfo()
{
var data = new ObservableCollection<NavigationMenuButtonTemplateViewModel>();
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/SatSunBonusWhite256x256.png",
MenuName = "PRIMAS SABATINAS Y DOMINICALES",
BenefitKind = "Subscripción",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/OverTimeMoneyWhite256x256.png",
MenuName = "JORNADAS EXTRAORDINARIAS",
BenefitKind = "Subscripción",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/VacationBonusWhite256x256.png",
MenuName = "PRIMA VACACIONAL",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/PecoWhite256x256.png",
MenuName = "PERMISOS ECONOMICOS",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/PunctualityBonusWhite256x256.png",
MenuName = "INCENTIVO PUNTUALIDAD Y ASISTENCIA",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/BonForSeniorityWhite256x256.png",
MenuName = "BONO DE ANTIGUEDAD",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
AddNavMenuButtonItem(data,
new NavigationMenuButtonTemplateViewModel(new ButtonInfo
{
Symbol = #"ms-appx:///Assets/AssetsMainMenuPage/WageIncreaseWhite256x256.png",
MenuName = "RETROACTIVO SUELDO",
BenefitKind = "Gratuito",
Status = "Vigente"
}));
return data;
}
private static void AddNavMenuButtonItem(ObservableCollection<NavigationMenuButtonTemplateViewModel> data, NavigationMenuButtonTemplateViewModel item)
{
data.Add(item);
}
}
also if you wish to style properties programatically you should do it at xaml.cs like for example like this one:
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
}
private void NavigationMenuIconImage_PointerEntered(object sender, PointerRoutedEventArgs e)
{
var image = (Image)sender;
var bitmapImage = image.Source as BitmapImage;
var uri = bitmapImage?.UriSource;
var uriPath = uri?.AbsolutePath;
var newUriPath = $#"ms-appx://{uriPath.Replace("White", "Black")}";
image.Source = new BitmapImage(new Uri(newUriPath, UriKind.RelativeOrAbsolute));
}
}
**SOLUTION No.2: **
another solution could be using usercontrols with dependency properties like this one:
<toolkitcontrols:Carousel x:Name="NavigationMenuCarouselPanel"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal"
ItemSource="{x:Bind ViewModel.MenuList}"
ItemMargin="25"
ItemDepth="160"
ItemRotationX="180"
ItemRotationY="25"
ItemRotationZ="0"
SelectedIndex="0"
Grid.Row="1">
<toolkitcontrols:Carousel.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</toolkitcontrols:Carousel.EasingFunction>
<usercontrolvm:NavigationMenuButtonTemplate ButtonInfo="{x:Bind ViewModel.MenuList[0],Mode=OneWay}"
NavigateToPageCommand = "{x:Bind ViewModel.NavigateToPageCommand}"/>
You will have to create a NavigationMenuButtonTemplate.xaml.cs with dependency properties like this one:
public sealed partial class NavigationMenuButtonTemplate : UserControl
{
public NavigationMenuButtonTemplate()
{
this.InitializeComponent();
}
public DelegateCommand NavigateToPageCommand
{
get { return (DelegateCommand)GetValue(NavigateToPageCommandProperty); }
set { SetValue(NavigateToPageCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for NavigateToPageCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty NavigateToPageCommandProperty =
DependencyProperty.Register("NavigateToPageCommand",
typeof(DelegateCommand),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
public ButtonInfo ButtonInfo
{
get { return (ButtonInfo)GetValue(ButtonInfoProperty); }
set { SetValue(ButtonInfoProperty, value); }
}
// Using a DependencyProperty as the backing store for ButtonInfo. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ButtonInfoProperty =
DependencyProperty.Register("ButtonInfo",
typeof(ButtonInfo),
typeof(NavigationMenuButtonTemplate),
new PropertyMetadata(0));
}
I dont like this solution because I'll have to repeat code at xaml file but its a good choice too.
Hope you like my answer I think it can be used by many of us and its appliable to many other controls with your endless imagination.
In WPF I am trying to draw graph lines in a UserControl. I will need to scale the X and Y values by different amounts.
I have a sample project I will include below. The problem is that when I use RenderTransform to scale the graph line, the LineThickness is also scaled. This would normally not be a problem because I could descale the line thickness by the inverse of the scaled value:
LineThickness = desiredLineThickness * (1/scaledValue)
Unfortunately, in this application I will need to scale the X and Y dimensions by very different values, and LineThickness is not separated into X and Y values.
In my example below I am drawing a sine wave that is scaled to the size of the drawing surface of the user control. You can see that the peaks and valleys extend beyond the drawing surface. This is because the original data goes from 1.0 to -1.0 and is scaled to the height of the drawing surface and translated to the middle of the drawing surface.
To demonstrate it more clearly, I also draw a blue line from 0,0 to 90,90 and then from 90,90 horizontally to the right. This line is also scaled to the height of the drawing surface. You can see the horizontal line is very thick.
I need a way to descale these lines in the Y axis separately from the X axis which is not scaled in this case.
I have tried several approaches including making a contained object of lines. I was thinking I could put the endpoints of the lines in the scaled position and then when drawing the actual lines, I would use a render transform to descale the Y values, but I could never get that to work because of the whole binding proxy problem.
Anyone have any ideas or have been faced with this before? I can't be the first with this problem.
Here is the code:
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
private void DrawingSurface_SizeChanged(object sender, SizeChangedEventArgs e)
{
foreach (GraphPen graphPen in GraphPens)
{
graphPen.SetDrawingDimensions(e.NewSize.Height, e.NewSize.Width);
}
}
}
}
UserControl.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}">
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas>
<Canvas.RenderTransform>
<TransformGroup>
<TransformGroup.Children>
<ScaleTransform
CenterX="0.0"
CenterY="0.0"
ScaleX="1.0"
ScaleY="{Binding ScaleHeight}"/>
<TranslateTransform
X="0.0"
Y="{Binding TranslateHeight}"/>
</TransformGroup.Children>
</TransformGroup>
</Canvas.RenderTransform>
</Canvas>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemsSource>
<CompositeCollection>
<Path
Data="{Binding PenGeometry}"
StrokeThickness="{Binding PenLineThickness}"
Stroke="{Binding
PenLineColor,
PresentationTraceSources.TraceLevel=None}"
/>
</CompositeCollection>
</ItemsControl.ItemsSource>
</ItemsControl>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<ItemsControl x:Name="DrawingSurface" Grid.Column="0" Grid.Row="0" SizeChanged="DrawingSurface_SizeChanged">
<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>
GraphPen.cs:
namespace WpfExampleControlLibrary
{
public class GraphPen : DependencyObject
{
private double _drawingSurfaceHeight = 100;
#region Constructor
public GraphPen()
{
PenGeometry = new PathGeometry();
PenGeometry.Figures.Add(new PathFigure());
}
#endregion Constructor
#region Dependency Properties
// LineColor
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); }
}
// LineThickness
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); }
}
// PenYMin
public static PropertyMetadata PenYMinPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenYMinProperty
= DependencyProperty.Register(
"PenYMin",
typeof(Double),
typeof(GraphPen),
PenYMinPropertyMetadata);
public double PenYMin
{
get { return (double)GetValue(PenYMinProperty); }
set { SetValue(PenYMinProperty, value); }
}
// PenYMax
public static PropertyMetadata PenYMaxPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty PenYMaxProperty
= DependencyProperty.Register(
"PenYMax",
typeof(Double),
typeof(GraphPen),
PenYMaxPropertyMetadata);
public double PenYMax
{
get { return (double)GetValue(PenYMaxProperty); }
set { SetValue(PenYMaxProperty, value); }
}
// ScaleHeight
public static PropertyMetadata ScaleHeightPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty ScaleHeightProperty
= DependencyProperty.Register(
"ScaleHeight",
typeof(Double),
typeof(GraphPen),
ScaleHeightPropertyMetadata);
public double ScaleHeight
{
get { return (double)GetValue(ScaleHeightProperty); }
set { SetValue(ScaleHeightProperty, value); }
}
// TranslateHeight
public static PropertyMetadata TranslateHeightPropertyMetadata
= new PropertyMetadata(null);
public static DependencyProperty TranslateHeightProperty
= DependencyProperty.Register(
"TranslateHeight",
typeof(Double),
typeof(GraphPen),
TranslateHeightPropertyMetadata);
public double TranslateHeight
{
get { return (double)GetValue(TranslateHeightProperty); }
set { SetValue(TranslateHeightProperty, 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
public void SetDrawingDimensions(double height, double width)
{
double dataHeight;
_drawingSurfaceHeight = height;
dataHeight = PenYMax - PenYMin;
ScaleHeight = _drawingSurfaceHeight / dataHeight;
TranslateHeight = ScaleHeight * -PenYMin;
}
}
}
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 GraphPen _graphPen1 = null;
private Int32 _pos = 0;
private bool _firstTime = true;
public MainWindow()
{
InitializeComponent();
_graphPen0 = new GraphPen();
_graphPen0.PenLineColor = Brushes.DarkGoldenrod;
_graphPen0.PenLineThickness = 1;
_graphPen0.PenYMax = 1.0;
_graphPen0.PenYMin = -1.0;
myExample.GraphPens.Add(_graphPen0);
_graphPen1 = new GraphPen();
_graphPen1.PenLineColor = Brushes.DarkBlue;
_graphPen1.PenLineThickness = 1;
_graphPen1.PenYMax = 10.0;
_graphPen1.PenYMin = 0.0;
myExample.GraphPens.Add(_graphPen1);
_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, Math.Sin(_pos % 360));
Point penPoint1 = new Point(0,0);
if (_pos <= 9) penPoint1 = new Point(_pos, _pos);
if (_pos > 9) penPoint1 = new Point(_pos, 9);
if (_firstTime)
{
myExample.GraphPens[0].PenGeometry.Figures[0].StartPoint = penPoint0;
myExample.GraphPens[1].PenGeometry.Figures[0].StartPoint = penPoint1;
_firstTime = false;
}
else
{
LineSegment segment0 = new LineSegment(penPoint0, true);
myExample.GraphPens[0].PenGeometry.Figures[0].Segments.Add(segment0);
LineSegment segment1 = new LineSegment(penPoint1, true);
myExample.GraphPens[1].PenGeometry.Figures[0].Segments.Add(segment1);
}
myExample.DebugText = _pos.ToString();
}
}
}
EDIT by poster
I forgot to show BindingProcy.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
}
}
EDIT by poster
Here is a screen shot:
I'm currently trying to implement some kind of camera-logic for a schoolproject( 2D Multiplayer-PacMan) using WPF(first time im using wpf). We have a Tile-Based Walkmap using a Canvas and an ItemControl, which is bigger than the actual screensize:
GameView.xaml
<controls:Camera HorizontalOffset="{Binding xPos}" VerticalOffset="{Binding yPos}">
<ItemsControl ItemsSource="{Binding Tiles}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding ImagePath}" Width="{Binding Size}" Height="{Binding Size}" />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</controls:Camera>
I'm trying to use a ScrollViewer for my Camera, but as you all know we can't bind The Horizontal and Vertical Offset to Properties due to the fact that they are readonly.
Thats why I created a UserControl named "Camera" which has a ScrollViewer and two DependencyProperties for Binding.
Camera.xaml
<UserControl x:Class="PacmanClient.UserControls.Camera"
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:PacmanClient.UserControls"
mc:Ignorable="d"
d:DesignHeight="1600" d:DesignWidth="1900">
<UserControl.Template>
<ControlTemplate TargetType="UserControl">
<ScrollViewer Name="cameraViewer" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible" IsEnabled="True">
<ContentPresenter/>
</ScrollViewer>
</ControlTemplate>
</UserControl.Template>
</UserControl>
Camera.xaml.cs
public partial class Camera : UserControl
{
ScrollViewer cameraViewer;
public Camera()
{
InitializeComponent();
}
#region HorizontalOffset
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
cameraViewer = this.Template.FindName("cameraViewer", this) as ScrollViewer;
}
public double HorizontalOffset
{
get
{
return (double)GetValue(HorizontalOffsetProperty);
}
set
{
SetValue(HorizontalOffsetProperty, value);
OnHorizontalOffsetChanged(value);
}
}
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached("HorizontalOffset", typeof(double), typeof(Camera),
new UIPropertyMetadata(0.0));
private void OnHorizontalOffsetChanged(double value)
{
cameraViewer.ScrollToHorizontalOffset(value);
}
#endregion
#region VerticalOffset
public double VerticalOffset
{
get
{
return (double)GetValue(VerticalOffsetProperty);
}
set
{
SetValue(VerticalOffsetProperty, value);
OnVerticalOffsetChanged(value);
}
}
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(double), typeof(Camera),
new UIPropertyMetadata(0.0));
private void OnVerticalOffsetChanged(double value)
{
cameraViewer.ScrollToVerticalOffset(value);
}
#endregion
}
Now I'm having two problems.
First:
When im trying to use my UserControl(as seen in GameView.xaml) and bind some Properties to the DependencyProperties, I'm getting the Error that those members are not recognized or accessible.( I actually thought i fixed this, but now it's back.) This has to be an AccessProblem, because autocompletion actually suggests me HorinzontalOffset and VerticalOffset.
I just can't find a solution.
And Second:
In the Version where I was able to access those Properties and successfully bind some Properties to them, the Values of the DependencyProperties never changed when the properties bound to them changed. I checked it via debugging and the setter of the code behind property is never called.
I'm hoping you can help me with those problems, i have no idea why it isn't working.
[Edit]
MainWindow.Xaml
<Window x:Class="WpfApplication3.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:local="clr-namespace:WpfApplication3"
xmlns:test="clr-namespace:test"
mc:Ignorable="d"
Title="MainWindow" Height="1600" Width="1900">
<StackPanel>
<Button Content="yolo" Click="Button_Click"></Button>
<ScrollViewer Name ="hallo" Height="1600" Width="1600" test:ScrollViewerExtension.HorizontalOffset = "{Binding xPos}" test:ScrollViewerExtension.VerticalOffset="{Binding yPos}" HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Visible">
<Canvas Height="3000" Width="3000">
<Ellipse Name="e1" Height="42" Width="42" Fill="Yellow"></Ellipse>
</Canvas>
</ScrollViewer>
</StackPanel>
</Window>
Mainwindow.cs
namespace WpfApplication3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public double xPos
{
get;
set;
}
public double yPos
{
get;
set;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
xPos += 50.0;
yPos += 50.0;
Canvas.SetTop(e1, yPos);
Canvas.SetLeft(e1, xPos);
}
}
}
If you craete custom control, you should create DependencyProperty. You created AttachedProperty, that's something else.
I will show you, how to use attached property:
<ScrollViewer x:Name="ScrollViewer1" Height="100" Width="150"
HorizontalScrollBarVisibility="Auto"
local:ScrollViewerExtension.HorizontalOffset="{Binding Value, ElementName=Slider1}">
<Rectangle Height="80" Width="100" Margin="100,50,0,0" Fill="Red"/>
</ScrollViewer>
<!-- you can databind ScrollViewerExtension.HorizontalOffset to whatever,
Slider is just for demonstration purposes -->
<Slider x:Name="Slider1"
Maximum="{Binding ElementName=ScrollViewer1, Path=ScrollableWidth}" />
and the attached property definition:
public static class ScrollViewerExtension
{
public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.RegisterAttached("HorizontalOffset", typeof (double), typeof (ScrollViewerExtension),
new PropertyMetadata(HorizontalOffsetChanged));
private static void HorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var scrollViewer = (ScrollViewer) d;
scrollViewer.ScrollToHorizontalOffset((double)e.NewValue);
}
public static void SetHorizontalOffset(DependencyObject element, double value)
{
element.SetValue(HorizontalOffsetProperty, value);
}
public static double GetHorizontalOffset(DependencyObject element)
{
return (double) element.GetValue(HorizontalOffsetProperty);
}
}
as you can see, attached properties should be used with existing controls. If you create new control, use dependency properties
Well, let's see if this helps you. Your first problem is difficult to solve without seeing your full code, because what you have shown here does not have any problem (in fact, i made a test solution copying/pasting this code and it worked). The second problem has to do with how you have defined the attached properties.
In the third parameter of RegisterAttached you should add a callback to your propertyChanged event handling method, like this:
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached("HorizontalOffset", typeof(object), typeof(Camera),new UIPropertyMetadata(null, HorizontalOffsetPropertyChanged));
Also you need to add two methods for getting/setting this property value:
public static object GetHorizontalOffset(DependencyObject obj)
{
return (string)obj.GetValue(HorizontalOffsetProperty);
}
public static void SetHorizontalOffset(DependencyObject obj, object value)
{
obj.SetValue(HorizontalOffsetProperty, value);
}
This is the full code for your usercontrol:
public partial class Camera : UserControl
{
public Camera()
{
InitializeComponent();
}
#region HorizontalOffset
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
public static readonly DependencyProperty HorizontalOffsetProperty =
DependencyProperty.RegisterAttached("HorizontalOffset", typeof(object), typeof(Camera),new UIPropertyMetadata(null, HorizontalOffsetPropertyChanged));
public static object GetHorizontalOffset(DependencyObject obj)
{
return (string)obj.GetValue(HorizontalOffsetProperty);
}
public static void SetHorizontalOffset(DependencyObject obj, object value)
{
obj.SetValue(HorizontalOffsetProperty, value);
}
public static void HorizontalOffsetPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
Camera cam = o as Camera;
ScrollViewer scroll=cam.Template.FindName("cameraViewer", cam) as ScrollViewer;
double horizontal = 0;
if (e.NewValue is double)
{
horizontal =(double) e.NewValue;
}
scroll.ScrollToHorizontalOffset(horizontal);
}
#endregion
#region VerticalOffset
public static readonly DependencyProperty VerticalOffsetProperty =
DependencyProperty.RegisterAttached("VerticalOffset", typeof(object), typeof(Camera), new UIPropertyMetadata(null, VerticalOffsetPropertyChanged));
public static object GetVerticalOffset(DependencyObject obj)
{
return (string)obj.GetValue(VerticalOffsetProperty);
}
public static void SetVerticalOffset(DependencyObject obj, object value)
{
obj.SetValue(VerticalOffsetProperty, value);
}
public static void VerticalOffsetPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
Camera cam = o as Camera;
ScrollViewer scroll = cam.Template.FindName("cameraViewer", cam) as ScrollViewer;
double vertical = 0;
if (e.NewValue is double)
{
vertical = (double)e.NewValue;
}
scroll.ScrollToVerticalOffset(vertical);
}
#endregion
}
Hope this gets you on the track
I'm trying to create a button user control which can display Image from xaml by adding property (ShowImage="ImagePath").
I've bound the user control Image source to the button's Content in the xaml file:
<UserControl x:Class="testUserControl.UserControls.TestDependencyShowImage"
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="auto" Width="auto">
<Grid>
<Button MinHeight="30" MinWidth="50">
<Button.Template>
<ControlTemplate TargetType="Button">
<Image Source="{TemplateBinding Content}"/>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</UserControl>
And I've created a Dependency property which create BitmapImage and set it to the content(in the meantime hard coded path just to see if it can be done).
cs:
namespace testUserControl.UserControls
{
/// <summary>
/// Interaction logic for TestDependencyShowImage.xaml
/// </summary>
public partial class TestDependencyShowImage : UserControl
{
private static BitmapImage s_oImage = null;
private static string s_strSourceImage = null;
public static readonly DependencyProperty ShowImageDP = DependencyProperty.Register("ShowImage", typeof(string), typeof(TestDependencyShowImage), new PropertyMetadata(null, new PropertyChangedCallback(SetImage)));
public string ShowImage
{
get
{
return (string)GetValue(ShowImageDP);
}
set
{
SetValue(ShowImageDP, value);
this.Content = s_oImage;
//OnTargetPowerChanged(this, new DependencyPropertyChangedEventArgs(TargetPowerProperty, value, value)); // Old value irrelevant.
}
}
private static void SetImage(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
TestDependencyShowImage muc = (TestDependencyShowImage)obj;
s_strSourceImage = (string)args.NewValue;
if (s_strSourceImage != null)
{
s_oImage = new BitmapImage(new Uri(#"C:\Users\AmitL\Desktop\james-brown-010.jpg", UriKind.Absolute));
//BitmapImage l_oImage = new BitmapImage(new Uri(value));
}
}
public TestDependencyShowImage()
{
InitializeComponent();
this.Content = s_oImage;
}
}
}
You really don't need any of that code. Just let the Framework convert the string file path into the image for you. Try this code inside your UserControl:
<Image Source="{Binding ShowImage, RelativeSource={RelativeSource
AncestorType={x:Type YourXamlNamespacePrefix:TestDependencyShowImage}}}" />