I have a UserControl that works perfectly at run time except the default values. I have used the same UserControl (MaterialUserControl) in two different Tabs in the MainWindow. In one of the Tabs it's default values (RadioButton isChecked values) work fine but not the other?
//This is the one which is not setting its default values!
<l:MaterialUserControl x:Name="materialShellUC" stainlessSteelBool="True" Click="shellMaterialBtn_Click" plateBool="True" ENBool="True"/>
//This one is in another Tab
<l:MaterialUserControl x:Name="materialDishUC" Width="500" HorizontalAlignment="Left" Click="materialDish_Click" plateBool="True" stainlessSteelBool="True" ENBool="True"/>
public partial class MaterialUserControl : UserControl
{
public MaterialUserControl()
{
InitializeComponent();
rootGrid.DataContext = this;
}
public static readonly DependencyProperty MaterialProperty =
DependencyProperty.Register("Material", typeof(string),
typeof(MaterialUserControl), new PropertyMetadata(""));
public String Material
{
get { return (String)GetValue(MaterialProperty); }
set { SetValue(MaterialProperty, value); }
}
public static readonly DependencyProperty MaterialNoProperty =
DependencyProperty.Register("MaterialNo", typeof(string),
typeof(MaterialUserControl), new PropertyMetadata(""));
public String MaterialNo
{
get { return (String)GetValue(MaterialNoProperty); }
set { SetValue(MaterialNoProperty, value); }
}
public event RoutedEventHandler Click;
void onButtonClick(object sender, RoutedEventArgs e)
{
if (this.Click != null)
this.Click(this, e);
}
public static readonly DependencyProperty stainlessSteelBoolProperty =
DependencyProperty.Register("strainlessSteelBool", typeof(bool), typeof(MaterialUserControl), new PropertyMetadata(null));
public bool stainlessSteelBool
{
get { return (bool)GetValue(stainlessSteelBoolProperty); }
set { SetValue(stainlessSteelBoolProperty, value); }
}
public static readonly DependencyProperty plateBoolProperty =
DependencyProperty.Register("plateBool", typeof(bool), typeof(MaterialUserControl), new PropertyMetadata(null));
public bool plateBool
{
get { return (bool)GetValue(plateBoolProperty); }
set { SetValue(plateBoolProperty, value); }
}
public static readonly DependencyProperty ENBoolProperty =
DependencyProperty.Register("ENBool", typeof(bool), typeof(MaterialUserControl), new PropertyMetadata(null));
public bool ENBool
{
get { return (bool)GetValue(ENBoolProperty); }
set { SetValue(ENBoolProperty, value); }
}
}
//here is the UserControl XAML
<Grid Background="Pink" x:Name="rootGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="Material" Grid.Row="0" Foreground="DarkBlue"/>
<RadioButton Content="ASME" Grid.Row="1" GroupName="a"/>
<RadioButton Content="EN" Grid.Row="1" Grid.Column="1" GroupName="a" IsChecked="{Binding ENBool}"/>
<RadioButton Content="Plate" IsChecked="{Binding plateBool}" Grid.Row="2" GroupName="b"/>
<RadioButton Content="Tube" Grid.Row="2" Grid.Column="1" GroupName="b"/>
<RadioButton Content="Stainless steel" Grid.Row="3" GroupName="c" IsChecked="{Binding stainlessSteelBool}"/>
<RadioButton Content="Carbon Steel" Grid.Row="3" Grid.Column="1" GroupName="c"/>
<Button Content="Material" Grid.Row="4" Click="onButtonClick"/>
<TextBox Grid.Row="4" Grid.Column="1" IsReadOnly="True" Text="{Binding Path=Material}"/>
<TextBox Grid.Row="4" Grid.Column="2" IsReadOnly="True" Text="{Binding Path=MaterialNo}"/>
</Grid>
Is it something to do with the DataContext?
Changes appear in the VS designer but all the RadioButtons value stays null.
Because you are setting the default values to the dependency properties to be null. Instead of null, set them to false (or true depending on your requirements)
public static readonly DependencyProperty plateBoolProperty =
DependencyProperty.Register("plateBool", typeof(bool), typeof(MaterialUserControl), new PropertyMetadata(false));
I would guess this is because the radio buttons in one tab use the same GroupName as the radio buttons in another tab, so they are considered part of the same logical group even though they are spread across tabs: when you select a radio button in one tab, you deselect a radio button in the corresponding group of another tab. Try generating unique group names.
Also, null is not a valid default value for a property of type bool; either make the default value false, or migrate your properties to type bool?.
Related
I have an UserControl that have Object Propdp:
public partial class HeaderControl : UserControl
{
public HeaderControl()
{
InitializeComponent();
DataContext = this;
}
public static readonly DependencyProperty ControlContentProperty =
DependencyProperty.Register(nameof(ControlContent), typeof(object),
typeof(HeaderControl), new PropertyMetadata(null));
public object ControlContent
{
get { return (object)GetValue(ControlContentProperty); }
set { SetValue(ControlContentProperty, value); }
}
}
In Xaml file:
<Grid Grid.Row="1">
<ContentControl Content="{Binding ControlContent, ElementName=root}"/>
</Grid>
if i use it without give a Name to the Button inside it works:
<ctrl:HeaderContorl >
<ctrl:HeaderContorl.ControlContent>
<Button Content="Hallooo" FontSize="40"
Foreground="Black" Height="100"/>
</ctrl:HeaderContorl.ControlContent>
</ctrl:HeaderContorl>
But if i name the Button inside it not works:
<ctrl:HeaderContorl >
<ctrl:HeaderContorl.ControlContent>
<Button x:Name="Btn" Content="Hallooo" FontSize="40"
Foreground="Black" Height="100"/>
</ctrl:HeaderContorl.ControlContent>
</ctrl:HeaderContorl>
Can someone please tell me why I can't name the button inside?
Working on a ComboBox that displays a list of available tile backgrounds. This is just a simple ComboBox with an ItemSource set to a collection of MapTileBackground objects.
The MapTileBackground class is defined entirely with properties:
public partial class MapTileBackground
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public byte[] Content { get; set; }
public Nullable<int> Color { get; set; }
public int StrokeColor { get; set; }
public byte StrokeThickness { get; set; }
}
which is defined in a separate library and I would prefer to not change it.
I have defined a simple shape extension to draw the background::
public class MapTileBackgroundPreview : Shape
{
public static readonly DependencyProperty SizeProperty = DependencyProperty.Register("Size", typeof(Point), typeof(MapTileBackgroundPreview));
public static readonly DependencyProperty TileBackgroundProperty = DependencyProperty.Register("TileBackground", typeof(MapTileBackground), typeof(MapTileBackgroundPreview));
public MapTileBackgroundPreview()
{
layout = new Hex.Layout(Hex.Orientation.Flat, new Hex.Point(8, 8), new Hex.Point(4, 4));
Size = new Point(8, 8);
TileBackground = null;
}
private Hex.Layout layout;
protected override Geometry DefiningGeometry
{
get
{
var points = layout.HexCorners(0, 0).ToArray();
var path = new PathFigure();
path.StartPoint = points[5].ToWin();
for (var i = 0; i < 6; i++)
path.Segments.Add(new LineSegment(points[i].ToWin(), true));
var geo = new PathGeometry();
geo.Figures.Add(path);
return geo;
}
}
public Point Size
{
get
{
return (Point)GetValue(SizeProperty);
}
set
{
SetValue(SizeProperty, value);
layout.Size = value.ToHex();
layout.Origin = new Hex.Point(layout.Size.X / 2, layout.Size.Y / 2);
}
}
public MapTileBackground TileBackground
{
get
{
return (MapTileBackground)GetValue(TileBackgroundProperty);
}
set
{
SetValue(TileBackgroundProperty, value);
if (value == null)
{
Fill = Brushes.Transparent;
Stroke = Brushes.Black;
StrokeThickness = 1;
}
else
{
Stroke = value.Stroke();
StrokeThickness = value.StrokeThickness();
Fill = value.Fill(layout.Orientation);
}
}
}
}
The layout is just a conversion utility between screen pixel coordinates and a hexagonal system. DefiningGeometry just add 6 line segments of the hex. The TileBackground setter, when given a not null MapTileBackground, updates the Stroke and Fill as the background defines. I've tested this control successfully (outside the combo box data template).
And speaking of:
<DataTemplate x:Key="TileListItemRenderer">
<Grid Width="225">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="75"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<local:MapTileBackgroundPreview Grid.Row="0" Grid.Column="0" Size="12,12" VerticalAlignment="Center" HorizontalAlignment="Center" TileBackground="{Binding /}"/>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
So I just create a shape, and two labels, bind the shape to the current MapTileBackground object (combo box ItemSource is a collection of MapTileBackground objects), and the labels to Name and Description.
My problem is the shape is always drawn empty (as in TileBackground is null) and the setter is never invoked. Both the Name Label and Description TextBlock behave as expected (display correct text). And during my debugging attempts, I created an id property on the preview object which in turn invokes the TileBackground Setter and bound it to the Id property (avoid current object bind), again, the TileBackgroundId setter is never invoked. I even added a new label bound to Id to see if that was working and it displays the id as expected. Here are those changes that again did not work. The TileBackgroundId or TileBackground properties are never set when opening the drop down.
<DataTemplate x:Key="TileListItemRenderer">
<Grid Width="225">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="75"/>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<local:MapTileBackgroundPreview Grid.Row="0" Grid.Column="0" Size="12,12" VerticalAlignment="Center" HorizontalAlignment="Center" TileBackgroundId="{Binding Id}"/>
<Label Grid.Row="0" Grid.Column="0" Content="{Binding Id}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<Label Grid.Row="0" Grid.Column="1" Content="{Binding Name}" HorizontalAlignment="Left" VerticalAlignment="Center" FontWeight="Bold"/>
<TextBlock Grid.Row="0" Grid.Column="2" Text="{Binding Description}" HorizontalAlignment="Stretch" VerticalAlignment="Top" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
public static readonly DependencyProperty TileBackgroundIdProperty = DependencyProperty.Register("TileBackgroundId", typeof(int), typeof(MapTileBackgroundPreview));
public int TileBackgroundId
{
get
{
return (int)GetValue(TileBackgroundIdProperty);
}
set
{
SetValue(TileBackgroundIdProperty, value);
TileBackground = TMapTileBackgroundTool.Get(value);
}
}
TMapTileBackgroundTool.Get() returns the correct object based on Id.
I have also tested instances of MapTileBackgroundPreview setting TileBackgroundId outside the data template.
Any thoughts as to what is going on?
The setter of the CLR wrapper for the dependency property is not supposed to be set as the WPF binding engine calls the GetValue and SetValue methods directly:
Setters not run on Dependency Properties?
Why are .NET property wrappers bypassed at runtime when setting dependency properties in XAML?
The getter and setter of the CLR wrapper property should only call the GetValue and SetValue method respectively.
If you want to do something when the dependency property is set, you should register a callback:
public static readonly DependencyProperty TileBackgroundIdProperty = DependencyProperty.Register("TileBackgroundId", typeof(int), typeof(MapTileBackgroundPreview),
new PropertyMetadata(0, new PropertyChangedCallback(TileBackgroundIdChanged)));
public int TileBackgroundId
{
get
{
return (int)GetValue(TileBackgroundIdProperty);
}
set
{
SetValue(TileBackgroundIdProperty, value);
}
}
private static void TileBackgroundIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MapTileBackgroundPreview ctrl = (MapTileBackgroundPreview)d;
ctrl.TileBackground = TMapTileBackgroundTool.Get((int)e.NewValue);
}
I'm new to WPF following an attempt to move from winforms so this is basic question however i'm trying to add to a string from a button click. The scenario is basically a keypad full of buttons (0 - 9) and the user presses these as to enter a code as a type of PIN. The method of going about this is non-negotiable so i cannot just replace with a text box and have the user type this. I have a little knowledge on bindings however it appears to be the appending to a string which is throwing me out as, obviously, all the buttons (0-9) need to be able to append to this string in sequence. So anyway, i have a ViewModel with a property called 'EnteredCode' and the buttons reside in a grid named 'buttonsGrid'. Would i be correct in thinking i handle the ButtonBase.Click event at the Grid level, determine which one was clicked and then append to the string? The appending to the string is obviously the problem here which i need help with but just as general feedback to best practise!
Code examples would also be a huge help.
TIA
So, you can treat WPF just as windows forms and solve this in the codebehind MainWindow.xaml.cs
Example:
DemonstrationViewModel demoViewModel;
public MainWindow()
{
InitializeComponent();
demoViewModel = new DemonstrationViewModel();
DataContext = demoViewModel;
}
private void alsoDemoButton_Click(object sender, RoutedEventArgs e)
{
demoViewModel.EnteredCode += "Clicked";
}
However, with the mention of ViewModel in your statement, you are likely following a MVVM pattern and writing in the code behind is not recommended.
If you are following a MVVM pattern, ICommands would be one way to go.
Pseudocode-ish example
XAML
<Button x:Name="demoButton" Command="{Binding InsertCommand}"/>
ViewModel
#region Constructor
public DemonstrationViewModel()
{
InsertCommand = new RelayCommand(ExecuteInsert, CanExecuteInsert);
}
#endregion
private void ExecuteInsert()
{
EnteredCode += "Clicked! ";
}
Further reading on ICommand in MVVM
You must be trying to bind Password property of PasswordBox to your ViewModel property. Password property is not bindable as it is not a DependencyProperty. This is for security reasons.
However if you want to make it bindable, you have to use a custom AttachedProperty. Now, you are trying to input PIN by pressing buttons like in ATM machines, and want your password bindable too. See a sample below to get you started.
xaml code :
<Window x:Class="WpfEvents._32802407.Win32802407"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pwd="clr-namespace:PasswordExtras"
Title="Win32802407" Height="354.136" Width="385.714">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="143*"/>
<ColumnDefinition Width="46*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="16*"/>
<RowDefinition Height="19*"/>
<RowDefinition Height="32*"/>
<RowDefinition Height="95*"/>
</Grid.RowDefinitions>
<TextBlock TextWrapping="Wrap" Text="Welcome Joshua !" FontSize="18" VerticalAlignment="Center" Margin="10,4,0,4"/>
<PasswordBox x:Name="pbPin" pwd:PasswordBoxAssistant.BindPassword="True" pwd:PasswordBoxAssistant.BoundPassword="{Binding Path=PIN, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center" Grid.Row="2" VerticalAlignment="Center" Width="120" FontSize="18" BorderThickness="0"/>
<StackPanel Grid.Column="1" HorizontalAlignment="Left" Height="170" Margin="10,10,0,0" Grid.Row="3" VerticalAlignment="Top" Width="72">
<Button Content="Done" Margin="0,15,0,0" Height="31"/>
<Button Content="Clear" Margin="0,15,0,0" Height="31"/>
<Button Content="Cancel" Margin="0,15,0,0" Height="31"/>
</StackPanel>
<WrapPanel ButtonBase.Click="NumericButtons_Click" HorizontalAlignment="Left" Height="147" Margin="10,23,0,0" Grid.Row="3" VerticalAlignment="Top" Width="266">
<Button Content="1" Width="75" Margin="5" Height="25"/>
<Button Content="2" Width="75" Margin="5" Height="25"/>
<Button Content="3" Width="75" Margin="5" Height="25"/>
<Button Content="4" Width="75" Margin="5" Height="25"/>
<Button Content="5" Width="75" Margin="5" Height="25"/>
<Button Content="6" Width="75" Margin="5" Height="25"/>
<Button Content="7" Width="75" Margin="5" Height="25"/>
<Button Content="8" Width="75" Margin="5" Height="25"/>
<Button Content="9" Width="75" Margin="5" Height="25"/>
<Button Content="0" Width="75" Margin="5" Height="25"/>
</WrapPanel>
<TextBlock HorizontalAlignment="Left" Margin="23,9,0,0" Grid.Row="1" TextWrapping="Wrap" Text="Enter your pin or press cancel" VerticalAlignment="Top"/>
</Grid>
</Window>
xaml code-behind :
using System;
using System.Windows;
using System.Windows.Controls;
using System.Diagnostics;
namespace WpfEvents._32802407
{
/// <summary>
/// Interaction logic for Win32802407.xaml
/// </summary>
public partial class Win32802407 : Window
{
ViewModelATM atm = new ViewModelATM();
public Win32802407()
{
InitializeComponent();
this.DataContext = atm;
}
private void NumericButtons_Click(object sender, RoutedEventArgs e)
{
string pwd = PasswordExtras.PasswordBoxAssistant.GetBoundPassword(pbPin);
if (pwd.Length == 4)
{
e.Handled = true;
return;
}
pwd = pwd + ((Button)e.OriginalSource).Content;
PasswordExtras.PasswordBoxAssistant.SetBoundPassword(pbPin, pwd);
Debug.WriteLine(pwd + " : " + atm.PIN);
}
}
}
namespace PasswordExtras
{
public static class PasswordBoxAssistant
{
public static readonly DependencyProperty BoundPassword =
DependencyProperty.RegisterAttached("BoundPassword", typeof(string), typeof(PasswordBoxAssistant), new PropertyMetadata(string.Empty, OnBoundPasswordChanged));
public static readonly DependencyProperty BindPassword = DependencyProperty.RegisterAttached(
"BindPassword", typeof (bool), typeof (PasswordBoxAssistant), new PropertyMetadata(false, OnBindPasswordChanged));
private static readonly DependencyProperty UpdatingPassword =
DependencyProperty.RegisterAttached("UpdatingPassword", typeof(bool), typeof(PasswordBoxAssistant), new PropertyMetadata(false));
private static void OnBoundPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PasswordBox box = d as PasswordBox;
// only handle this event when the property is attached to a PasswordBox
// and when the BindPassword attached property has been set to true
if (d == null || !GetBindPassword(d))
{
return;
}
// avoid recursive updating by ignoring the box's changed event
box.PasswordChanged -= HandlePasswordChanged;
string newPassword = (string)e.NewValue;
if (!GetUpdatingPassword(box))
{
box.Password = newPassword;
}
box.PasswordChanged += HandlePasswordChanged;
}
private static void OnBindPasswordChanged(DependencyObject dp, DependencyPropertyChangedEventArgs e)
{
// when the BindPassword attached property is set on a PasswordBox,
// start listening to its PasswordChanged event
PasswordBox box = dp as PasswordBox;
if (box == null)
{
return;
}
bool wasBound = (bool)(e.OldValue);
bool needToBind = (bool)(e.NewValue);
if (wasBound)
{
box.PasswordChanged -= HandlePasswordChanged;
}
if (needToBind)
{
box.PasswordChanged += HandlePasswordChanged;
}
}
private static void HandlePasswordChanged(object sender, RoutedEventArgs e)
{
PasswordBox box = sender as PasswordBox;
// set a flag to indicate that we're updating the password
SetUpdatingPassword(box, true);
// push the new password into the BoundPassword property
SetBoundPassword(box, box.Password);
SetUpdatingPassword(box, false);
}
public static void SetBindPassword(DependencyObject dp, bool value)
{
dp.SetValue(BindPassword, value);
}
public static bool GetBindPassword(DependencyObject dp)
{
return (bool)dp.GetValue(BindPassword);
}
public static string GetBoundPassword(DependencyObject dp)
{
return (string)dp.GetValue(BoundPassword);
}
public static void SetBoundPassword(DependencyObject dp, string value)
{
dp.SetValue(BoundPassword, value);
}
private static bool GetUpdatingPassword(DependencyObject dp)
{
return (bool)dp.GetValue(UpdatingPassword);
}
private static void SetUpdatingPassword(DependencyObject dp, bool value)
{
dp.SetValue(UpdatingPassword, value);
}
}
}
ViewModel
using System;
namespace WpfEvents._32802407
{
public class ViewModelATM
{
string _pin = "";
public string PIN { get { return _pin; } set { _pin = value; } }
}
}
I have tried to google this but I am not super clear on the suggestions I see people making.
I have 3 buttons within a user control that are exactly the same except for automation id and text. Instead of duplicating this code across all buttons, I would like to create a common control or control template that I can use. My problem is these buttons have nested controls whose text I need to change but I'm not sure how to do this.
<UserControl>
<Grid>
<StackPanel x:Name="myStackPanel" Grid.Row="1" Padding="0">
<Button AutomationProperties.AutomationId="CallButton" x:Name="CallButton" Style="{StaticResource ButtonStyle}">
<RelativePanel>
<SymbolIcon AutomationProperties.AutomationId="CallIcon" x:Name="CallIcon" Symbol="Phone" Style="{StaticResource SymbolStyle}" />
<StackPanel>
<TextBlock AutomationProperties.AutomationId="CallLabel" Text="Call" Style="{StaticResource LabelStyle}"/>
<TextBlock AutomationProperties.AutomationId="CallText" Text="123-456-7890" Style="{StaticResource ContentStyle}"/>
</StackPanel>
</RelativePanel>
</Button>
<Button AutomationProperties.AutomationId="EmailButton" x:Name="EmailButton" Style="{StaticResource ButtonStyle}">
<RelativePanel>
<SymbolIcon AutomationProperties.AutomationId="EmailIcon" x:Name="EmailIcon" Symbol="Mail" Style="{StaticResource SymbolStyle}"/>
<StackPanel >
<TextBlock AutomationProperties.AutomationId="EmailLabel" Text="Email" Style="{StaticResource LabelStyle}"/>
<TextBlock AutomationProperties.AutomationId="EmailText" Text="meetme#email.com" Style="{StaticResource ContentStyle}"/>
</StackPanel>
</RelativePanel>
</Button>
<Button AutomationProperties.AutomationId="WebsiteButton" x:Name="WebsiteButton" Style="{StaticResource ButtonStyle}">
<RelativePanel>
<SymbolIcon AutomationProperties.AutomationId="WebsiteIcon" x:Name="WebsiteIcon" Symbol="Link" Style="{StaticResource SymbolStyle}"/>
<StackPanel >
<TextBlock AutomationProperties.AutomationId="WebsiteLabel" Text="Website" Style="{StaticResource LabelStyle}"/>
<TextBlock AutomationProperties.AutomationId="WebsiteText" Text="http://meetme.com" Style="{StaticResource ContentStyle}"/>
</StackPanel>
</RelativePanel>
</Button>
</StackPanel>
</Grid>
</Grid>
As you can see, the code for all 3 buttons is same. All I want to do is create a control where I can set the automation ids and text properties of the nested controls.
Thanks!
You can create a button based on a UserControl (add a new UserControl). It will allow you to enjoy all the default button's properties, events and states (OnClick, Command, etc.) and to add your own properties, template and behavior.
Using dependency properties instead of simple properties is strongly advised if you want to use bindings or animations on them.
C#:
public partial class CustomButton : Button
{
#region IconAutomationId
public string IconAutomationId
{
get { return (string)GetValue(IconAutomationIdProperty); }
set { SetValue(IconAutomationIdProperty, value); }
}
public static readonly DependencyProperty IconAutomationIdProperty =
DependencyProperty.Register("IconAutomationId", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region LabelAutomationId
public string LabelAutomationId
{
get { return (string)GetValue(LabelAutomationIdProperty); }
set { SetValue(LabelAutomationIdProperty, value); }
}
public static readonly DependencyProperty LabelAutomationIdProperty =
DependencyProperty.Register("LabelAutomationId", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region TextAutomationId
public string TextAutomationId
{
get { return (string)GetValue(TextAutomationIdProperty); }
set { SetValue(TextAutomationIdProperty, value); }
}
public static readonly DependencyProperty TextAutomationIdProperty =
DependencyProperty.Register("TextAutomationId", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region Symbol
public object Symbol
{
get { return (object)GetValue(SymbolProperty); }
set { SetValue(SymbolProperty, value); }
}
public static readonly DependencyProperty SymbolProperty =
DependencyProperty.Register("Symbol", typeof(object), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region Label
public string Label
{
get { return (string)GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
#region Text
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(CustomButton), new PropertyMetadata(null));
#endregion
public CustomButton()
{
InitializeComponent();
}
}
XAML:
<Button x:Class="WpfApplication1.CustomButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="customButton">
<RelativePanel>
<SymbolIcon AutomationProperties.AutomationId="{Binding IconAutomationId, ElementName=customButton}"
Symbol="{Binding Symbol, ElementName=customButton}"
Style="{StaticResource SymbolStyle}"/>
<StackPanel >
<TextBlock AutomationProperties.AutomationId="{Binding LabelAutomationId, ElementName=customButton}"
Text="{Binding Label, ElementName=customButton}"
Style="{StaticResource LabelStyle}"/>
<TextBlock AutomationProperties.AutomationId="{Binding TextAutomationId, ElementName=customButton}"
Text="{Binding Text, ElementName=customButton}"
Style="{StaticResource ContentStyle}"/>
</StackPanel>
</RelativePanel>
</Button>
Use:
<local:CustomButton AutomationProperties.AutomationId="CallButton"
x:Name="CallButton"
Style="{StaticResource ButtonStyle}"
IconAutomationId="CallIcon"
LabelAutomationId="CallLabel"
TextAutomationId="CallText"
Symbol="Phone"
Label="Call"
Text="123-456-7890"/>
It may be more work than you feel like doing but it sounds like you're going to want to create a UserControl. It should inherit from Button in the code-behind:
public partial class MyButton: Button
In the XAML you will include essentially the guts of what you have in each of the buttons now.
The tedious part is that you will then (in the code-behind) need to create a DependencyProperty for each of the "properties" you will want to set in this control: for example, one for the CallIconAutomationId, one for the CallLabelAutomationId, one for the CallLabelText, etc. You will then bind each of these properties in the XAML to the dependency property. These properties become the data that you will set on each individual MyButton (your new UserControl).
Then, in the container that is hosting these controls (which appears to be another UserControl in your example above) you would set these custom properties on each of your new MyButton controls, which will look something like this:
<myNamespace:MyButton EmailIconAutomationId="EmailIcon" LabelAutomationId="EmailLabel" />
etc.
Basically, you're creating a new control (a UserControl) based on the Button control (which gives you most of your functionality) and adding new custom properties directly to that new control (which work just like all the other control properties you're accustomed to).
I have a UserControl which acts as a wrapper for a ContentControl, which is simply a title to the ContentControl.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Background="Green" Grid.Row="0">
<TextBlock Text="{Binding Header}" Style="{StaticResource HeaderStyle}" Margin="12, 10, 0, 10" />
</Grid>
<ContentControl HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Content="{Binding Body}" Grid.Row="1"/>
</Grid>
And here's where I try to use the control:
<gbl:ListHeader Grid.Row="1" Visibility="{Binding HasMovies, Converter={StaticResource VisibilityConverter}}" Header="{Binding Path=LocalizedResources.movie_list_header, Source={StaticResource LocalizedStrings}}" >
<gbl:ListHeader.Body>
<ListBox SelectionChanged="ListBoxContainerSelectionChanged" ItemsSource="{Binding Movies}" ItemContainerStyle="{StaticResource HeaderListBoxItemStyle}">
<ListBox.ItemTemplate>
<DataTemplate>
<gbl:MovieItemControl Header="{Binding MovieTitle}" Description="{Binding FormattedDescription}" Detail="{Binding FormattedDetail}" Opacity="{Binding IsSuppressed, Converter={StaticResource DimIfTrueConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</gbl:ListHeader.Body>
The DataBinding to the list happens, however nothing displays in the control. I'm guessing that it's still there, but too small to see (undefined h/w).
Is there something that I'm doing wrong? The header shows fine, so the control appears to be working somewhat.
Edit:
Here's the code-behind for ListHeader:
public partial class ListHeader : UserControl
{
private readonly ListHeaderData _data = new ListHeaderData();
public ListHeader()
{
InitializeComponent();
DataContext = _data;
}
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
// Using a DependencyProperty as the backing store for Header. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(ListHeader), new PropertyMetadata("",HeaderPropertyChanged) );
private static void HeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var lh = d as ListHeader;
if (lh != null)
lh._data.Header = e.NewValue as string;
}
public object Body
{
get { return GetValue(BodyProperty); }
set { SetValue(BodyProperty, value); }
}
// Using a DependencyProperty as the backing store for Body. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BodyProperty =
DependencyProperty.Register("Body", typeof(object), typeof(ListHeader), new PropertyMetadata(null, BodyPropertyChanged));
private static void BodyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var lh = d as ListHeader;
if (lh != null)
lh._data.Body = e.NewValue;
}
}
public class ListHeaderData : ViewModelBase
{
public ListHeaderData()
{
if (IsInDesignMode)
{
Header = "Custom Header Goes Here";
Body = new Grid() { Background = new SolidColorBrush(Colors.Yellow) };
}
}
private string _header;
public string Header
{
get { return _header; }
set { _header = value; RaisePropertyChanged("Header"); }
}
private object _body;
public object Body
{
get { return _body; }
set { _body = value; RaisePropertyChanged("Body");}
}
}
In addition to what i said in my comment you appear to bind to your DataContext in the UserControl declaration which is a Bad Thing and the problem of all this.
You appear to want to bind to the properties of the UserControl but you bind directly to the properties of the DataContext which is your ViewModel, hence setting the Body property on an instance in XAML does nothing as the property is sidestepped by the internal binding.
UserControls should for all i know do bindings like this:
<UserControl Name="control" ...>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Background="Green" Grid.Row="0">
<TextBlock Text="{Binding Header, ElementName=control}" Style="{StaticResource HeaderStyle}" Margin="12, 10, 0, 10" />
</Grid>
<ContentControl HorizontalAlignment="Stretch" HorizontalContentAlignment="Stretch" Content="{Binding Body, ElementName=control}" Grid.Row="1"/>
</Grid>
Get rid of those dependency property changed callbacks and change the property code in ViewModels to this format to make sure it changed:
private int _MyProperty = 0;
public int MyProperty
{
get { return _MyProperty; }
set
{
if (_MyProperty != value)
{
_MyProperty = value;
OnPropertyChanged("MyProperty");
}
}
}