How can I create a custom button control which takes the argument of path in the button constructor so i can reuse the control throughout my projects?
This is how i normally create buttons....
<Grid>
<Button Style="{DynamicResource ButtonPathStyle}" Width="24" Height="24">
<Path Fill="Gray" Data="M50,50 L100,100 L150,50" Stretch="Uniform" />
</Button>
</Grid>
However I would like to create a custom button so i could create them like this along with a custom style sheet
<Grid>
<PathButton Width="24" Height="24"
Data="M50,50 L100,100 L150,50"
Stretch="Uniform" />
</Grid>
If anyone knows of any good resources of can even whip together a quick example it'd be greatly appreciated. I would assume creating the button would be pretty simple as a custom control.
And by custom control i mean compile into a DLL i can share.
Code Behind:
public class PathButton : Button
{
public static DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(Geometry), typeof(PathButton), new FrameworkPropertyMetadata(new PropertyChangedCallback(Data_Changed)));
public Geometry Data
{
get { return (Geometry)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
private static void Data_Changed(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
PathButton thisClass = (PathButton)o;
thisClass.SetData();
}
private void SetData()
{
Path path = new Path();
path.Data = Data;
path.Stroke = this.Foreground;
path.StrokeThickness = 1;
this.Content = path;
}
}
XAML:
<Window x:Class="WpfApplication2.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:WpfApplication2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:PathButton Data="M50,50 L100,100 L150,50" />
</Grid>
Here you go
public class LazyButton : Button
{
public static readonly DependencyProperty DataProperty = DependencyProperty.Register("Data", typeof(Geometry), typeof(LazyButton), new PropertyMetadata(new PropertyChangedCallback(OnDataChanged)));
private static void OnDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
LazyButton button = d as LazyButton;
button.Content = new Path() { Data = e.NewValue as Geometry, Fill = Brushes.Gray, Stretch = Stretch.Uniform };
}
public Geometry Data
{
get { return (Geometry)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
}
This is usable in xaml:
<local:LazyButton Data="M50,50 L100,100 L150,50"></local:LazyButton>
I'm sure you can figure out how to make the Fill and Stretch options settable in xaml as well
Related
I created a Dependency Property in the usercontrol, but however changes in the usercontrol was NOT notified to the Viewmodel
Usercontrol
<UserControl x:Class="DPsample.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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBox x:Name="txtName"></TextBox>
</Grid>
UserControl.cs
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
#region SampleProperty
public static readonly DependencyProperty SamplePropertyProperty
= DependencyProperty.Register("SampleProperty",
typeof(string),
typeof(UserControl1),
new PropertyMetadata(OnSamplePropertyChanged));
public string SampleProperty
{
get { return (string)GetValue(SamplePropertyProperty); }
set { SetValue(SamplePropertyProperty, value); }
}
static void OnSamplePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
(obj as UserControl1).OnSamplePropertyChanged(e);
}
private void OnSamplePropertyChanged(DependencyPropertyChangedEventArgs e)
{
string SamplePropertyNewValue = (string)e.NewValue;
txtName.Text = SamplePropertyNewValue;
}
#endregion
}
MainWindow
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DPsample" x:Class="DPsample.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 SampleProperty="{Binding SampleText,Mode=TwoWay}" HorizontalAlignment="Left" Margin="76,89,0,0" VerticalAlignment="Top" Width="99"/>
<Button Content="Show" HorizontalAlignment="Left" Margin="76,125,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
MainWindow.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var item = this.DataContext as MainViewModel;
MessageBox.Show(item.SampleText.ToString());
}
MainViewModel.cs
public class MainViewModel : NotifyViewModelBase
{
public MainViewModel()
{
this.SampleText = "test";
}
private string _sampleText;
public string SampleText
{
get { return _sampleText; }
set { _sampleText = value; OnPropertyChanged("SampleText"); }
}
}
Bind the TextBox.Text property in the UserControl to its SampleProperty like this:
<TextBox Text="{Binding SampleProperty,
RelativeSource={RelativeSource AncestorType=UserControl}}"/>
Now you could simply remove your OnSamplePropertyChanged callback.
You might also register SampleProperty to bind two-way by default like this:
public static readonly DependencyProperty
SamplePropertyProperty = DependencyProperty.Register(
"SampleProperty", typeof(string), typeof(UserControl1),
new FrameworkPropertyMetadata(
null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
An alternative way to do this is an ElementName Binding. First assign the x:Name attribute to the UserControl (for example x:Name="MyUC"), then change the binding to:
<TextBox Text="{Binding ElementName=MyUC, Path=SampleProperty}"/>
I'm trying to bind a user control property "MyUserControl.Names" to a collection property "Names" of the main window. It doesn't work if I do it in ItemsControl template, but it works if I move the control definition out of the ItemsControl template. Here is the xaml:
<Window x:Class="TestItemsControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestItemsControl"
Height="200" Width="200"
Name="MainControl">
<StackPanel>
<ItemsControl ItemsSource="{Binding Groups, ElementName=MainControl}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- This doesn't work -->
<StackPanel Background="WhiteSmoke" Height="40" Width="100" Margin="5" HorizontalAlignment="Left">
<TextBlock Text="{Binding .}"/>
<local:MyUserControl Names="{Binding Names, ElementName=MainControl}"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- This works -->
<StackPanel Background="WhiteSmoke" Height="40" Width="100" Margin="5" HorizontalAlignment="Left">
<TextBlock Text="Group3"/>
<local:MyUserControl Names="{Binding Names, ElementName=MainControl}"/>
</StackPanel>
</StackPanel>
</Window>
MainWindow.xaml.cs contains two dependency properties:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SetValue(GroupsProperty, new ObservableCollection<string>());
SetValue(NamesProperty, new ObservableCollection<string>());
Groups.Add("Group1");
Groups.Add("Group2");
Names.Add("Name1");
Names.Add("Name2");
}
public static readonly DependencyProperty GroupsProperty =
DependencyProperty.Register("Groups", typeof(ObservableCollection<string>), typeof(MainWindow),
new PropertyMetadata(null));
public ObservableCollection<string> Groups
{
get { return (ObservableCollection<string>)GetValue(GroupsProperty); }
set { SetValue(GroupsProperty, value); }
}
public static readonly DependencyProperty NamesProperty =
DependencyProperty.Register("Names", typeof(ObservableCollection<string>), typeof(MainWindow),
new PropertyMetadata(null));
public ObservableCollection<string> Names
{
get { return (ObservableCollection<string>)GetValue(NamesProperty); }
set { SetValue(NamesProperty, value); }
}
}
Here is the result:
The first two rectangles are what ItemsControl generates. The third one is what I have manually added right after the ItemsControl. As you can see, even though the code is exactly the same in both cases, the first two rectangles don't have names, but the third one has. Is there any reason why wouldn't it work with ItemsControl?
Edit:
Here is the code of the MyUserControl.xaml.cs:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
SetValue(NamesProperty, new ObservableCollection<string>());
}
public static readonly DependencyProperty NamesProperty = DependencyProperty.Register(
"Names", typeof(ObservableCollection<string>), typeof(MyUserControl),
new PropertyMetadata(null, NamesPropertyChanged));
public ObservableCollection<string> Names
{
get { return (ObservableCollection<string>)GetValue(NamesProperty); }
set { SetValue(NamesProperty, value); }
}
private static void NamesPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control = (MyUserControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
oldCollection.CollectionChanged -= control.NamesCollectionChanged;
if (newCollection != null)
newCollection.CollectionChanged += control.NamesCollectionChanged;
control.UpdateNames();
}
private void NamesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
UpdateNames();
}
private void UpdateNames()
{
NamesPanel.Children.Clear();
if (Names == null)
return;
foreach(var name in Names)
{
var textBlock = new TextBlock();
textBlock.Text = name + ", ";
NamesPanel.Children.Add(textBlock);
}
}
}
MyUserControl.xaml:
<UserControl x:Class="TestItemsControl.MyUserControl"
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:TestItemsControl"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Name="ParentControl">
<StackPanel Name="NamesPanel" Orientation="Horizontal"/>
</UserControl>
Replace SetValue in the UserControl's constructor by SetCurrentValue. It may even make sense not to assign an initial value at all for the Names property.
public MyUserControl()
{
InitializeComponent();
SetCurrentValue(NamesProperty, new ObservableCollection<string>());
}
SetValue (as opposed to SetCurrentValue) sets a so-called local value to the Names property. When you assign a Binding as in the second case, this is also considered a local value with the same precedence as the one set in the constructor.
However, in the first case, the Binding is set in a DataTemplate, where it doesn't count as a local value. Since it has lower precedence, it does not replace the initial value.
More details here: Dependency Property Value Precedence
In the code below, I have a UserControl that contains an ellipse and a textblock. I'd like to create a reusable control that I can bind to that allows me to set the text based on a string, and changes the fill color between Red/Green based on a boolean.
I can do this now by digging deep into the markup and using some complex binding, but I want to reuse this control in a list and it seemed easier to create a control for the purpose. However, I am not sure where to go next and if I should be creating dependency-properties that tie to the values for Fill and Text, or what.
<UserControl
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"
mc:Ignorable="d"
x:Class="Herp.Derp.View.DeliveryStatusIndicator"
x:Name="UserControl"
d:DesignWidth="91" d:DesignHeight="35">
<Grid x:Name="LayoutRoot">
<StackPanel Orientation="Horizontal">
<Ellipse Width="35" Height="35" Fill="Green">
<Ellipse.OpacityMask>
<VisualBrush Stretch="Fill" Visual="{StaticResource appbar_location_circle}"/>
</Ellipse.OpacityMask>
</Ellipse>
<TextBlock Style="{StaticResource Heading2}"
VerticalAlignment="Center" Margin="3,0,0,0">
<Run Text="FRONT"/>
</TextBlock>
</StackPanel>
</Grid>
</UserControl>
here how you can achieve this
UserControl
/// <summary>
/// Interaction logic for UserControl1.xaml
/// </summary>
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
public static readonly DependencyProperty FrontTextProperty = DependencyProperty.Register( "FrontText", typeof(string),typeof(UserControl1), new FrameworkPropertyMetadata(string.Empty));
public string FrontText
{
get { return (string)GetValue(FrontTextProperty); }
set {
SetValue(FrontTextProperty, value);
frontBlock.Text = value;
}
}
public static readonly DependencyProperty EllipseStateProperty = DependencyProperty.Register("EllipseState", typeof(bool), typeof(UserControl1), new FrameworkPropertyMetadata(false));
public bool EllipseState
{
get { return (bool)GetValue(EllipseStateProperty); }
set
{
SetValue(EllipseStateProperty, value);
if (value)
{
ellipse.Fill = new SolidColorBrush( Colors.Green);
}
else
{
ellipse.Fill = new SolidColorBrush(Colors.Red);
}
}
}
}
MainWindow
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 EllipseState="{Binding yourProperty }"/>
<CheckBox Content="CheckBox" HorizontalAlignment="Left" Margin="207,94,0,0" VerticalAlignment="Top"/>
</Grid>
</Window>
Yes, to create properties that "parent" XAML can assign bindings to, you need to create aDependencyProperty for each field that you want to bind to.
You would then bind your user control xaml to the backing property for the DP.
Here's what I got for a working solution:
public partial class DeliveryStatusIndicator : UserControl
{
public DeliveryStatusIndicator()
{
InitializeComponent();
}
public static readonly DependencyProperty DeliveryDescriptionProperty = DependencyProperty.Register("DeliveryDescription", typeof(string), typeof(DeliveryStatusIndicator), new FrameworkPropertyMetadata("Default", DescriptionChangedEventHandler));
private static void DescriptionChangedEventHandler(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DeliveryStatusIndicator)d).Desc.Text = (string)e.NewValue;
}
public string DeliveryDescription
{
get { return (string)GetValue(DeliveryDescriptionProperty); }
set
{
SetValue(DeliveryDescriptionProperty, value);
}
}
public static readonly DependencyProperty DeliveryStatusProperty = DependencyProperty.Register("DeliveryStatus", typeof(bool), typeof(DeliveryStatusIndicator), new FrameworkPropertyMetadata(false, StatusChangedEventHandler));
private static void StatusChangedEventHandler(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((DeliveryStatusIndicator)d).Indicator.Fill = (bool)e.NewValue ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red);
}
public bool DeliveryStatus
{
get { return (bool)GetValue(DeliveryStatusProperty); }
set
{
SetValue(DeliveryStatusProperty, value);
}
}
}
I created my own user control with dependency properties and added it to my main window, where I now want to be able set the dependency properties. The properties are not taking the value I set in the XAML of the main window and I am not sure what I am missing. In the code I set the default value for the FillBrush property to Yellow. In the XAML I set it to Red. When I click the Test button, it shows the property to be Yellow. Here is the code:
Window XAML
<Window x:Class="Test.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:test="clr-namespace:Test"
Title="Window2" Height="200" Width="600">
<StackPanel>
<test:TestUserControl x:Name="myControl"
FillBrush="Red" VerticalAlignment="Center" Margin="20"/>
<Button Height="24" Width="300" Content="Test" Click="Button_Click" />
<TextBox x:Name="debugTextBox" Margin="20"/>
</StackPanel>
</Window>
Window Code Behind
public partial class Window2 : Window
{
public static readonly DependencyProperty FillBrushProperty =
DependencyProperty.Register("FillBrush", typeof(Brush), typeof(Window2),
new UIPropertyMetadata(Brushes.Yellow));
public Brush FillBrush
{
get { return (Brush)GetValue(FillBrushProperty); }
set { SetValue(FillBrushProperty, value); }
}
public Window2() { InitializeComponent(); }
private void Button_Click(object sender, RoutedEventArgs e)
{
this.debugTextBox.Text =
"Red: " + Brushes.Red +
" Yellow: " + Brushes.Yellow +
" Actual: " + this.FillBrush;
}
}
User Control XAML
<UserControl x:Class="Test.TestUserControl"
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"
d:DesignHeight="300" d:DesignWidth="300">
<TextBlock Text="User Control"/>
</UserControl>
User Control Code Behind
public partial class TestUserControl : UserControl
{
public static readonly DependencyProperty FillBrushProperty =
DependencyProperty.Register("FillBrush", typeof(Brush), typeof(TestUserControl),
new UIPropertyMetadata(Brushes.Cyan));
public Brush FillBrush
{
get { return (Brush)GetValue(FillBrushProperty); }
set { SetValue(FillBrushProperty, value); }
}
public TestUserControl()
{
InitializeComponent();
}
}
What am I missing?
In the XAML you are setting the Fillbrush value on TestUserControl to Red, but when the button is clicked it shows the Fillbrush value for Window2, not TestUserControl. And since the Fillbrush value for Window2 is not set in XAML it still has the default value of Yellow.
I have a custom usercontrol with DataContext="{Binding RelativeSource={RelativeSource self}}"
On the code behind i've made a dependency property like:
public static DependencyProperty ElementNameProperty = DependencyProperty.Register("ElementName",
typeof(string),
typeof(ElementControl),
new PropertyMetadata(new PropertyChangedCallback((s, e) => { new Base().OnPropertyChanged("ElementName"); })));
public string ElementName
{
get
{
return (string)base.GetValue(ElementNameProperty);
}
set
{
base.SetValue(ElementNameProperty, value);
}
}
Now when I try to use this usercontrol in my mainpage.xaml and use the following binding: <test.TestControl ElementName="{Binding name}" />, it keeps searching for 'name' property in my custom usercontrol instead of where it should come from?
What am I doing wrong ?
It searches there because you have the DataContext set on the topmost level for your user control. What you would need to do is get rid of the relative binding to self in the user control and specify ElementName in bindings (inside user control). Btw you probably don't need OnPropertyChanged in the PropertyChangedCallback cause DependencyProperties in their nature notify about value changes.
I eventually solved it this way. Not the way I wanted, but it's a (in my eyes) pretty neat solution.
CustomUserControl.xaml
<UserControl x:Class="TestApp.Controls.CustomUserControl"
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"
mc:Ignorable="d"
Width="75"
Height="75">
<Canvas x:Name="LayoutRoot"
Background="Black">
<StackPanel Orientation="Vertical">
<Image x:Name="UCImage"
Width="50"
Height="50"
HorizontalAlignment="Center" />
<TextBlock x:Name="UCText"
HorizontalAlignment="Center" />
</StackPanel>
</Canvas>
</UserControl>
CustomUserControl.xaml.cs
public partial class ElementControl : UserControl
{
#region DependencyProperty ElementNameProperty
public static DependencyProperty ElementNameProperty = DependencyProperty.Register("ElementName",
typeof(string),
typeof(ElementControl),
new PropertyMetadata(new PropertyChangedCallback((s, e) =>
{
//See Here
((ElementControl)s).UCText.Text = e.NewValue as string;
})));
public string ElementName
{
get
{
return (string)base.GetValue(ElementNameProperty);
}
set
{
base.SetValue(ElementNameProperty, value);
}
}
#endregion
}