I have just started to learn WPF.
I have a button with image. like Image+Text
<Button Height="67" Name="Button1" Width="228" HorizontalContentAlignment="Left">
<StackPanel Orientation="Horizontal" >
<Image Source="Images/add.png" Stretch="Uniform"></Image>
<TextBlock Text=" Create Company" VerticalAlignment="Center" FontSize="20"></TextBlock>
</StackPanel>
</Button>
Now I want to add many more buttons in the above format.
So I have to write the same code again and again.
So I decided to have a customButton to do my job easily.
I tried to create the custom control.
I added a property named Image there.
Now how should I give value to that property?
Am I going on the wrong way?
Here you have tutorial how to create a custom control.
[1.] Add new item "Custom Control (WPF)" with name "ButtonImg".
After this step, VS create for you two files: "ButtonImg.cs" and "/Themes/Generic.xaml".
[2.] Add few dependency properties to "ButtonImg.cs" file:
I created properties to: image source, text, image width and height.
public class ButtonImg : Control
{
static ButtonImg()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(ButtonImg), new FrameworkPropertyMetadata(typeof(ButtonImg)));
}
public ImageSource ImageSource
{
get { return (ImageSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(ButtonImg), new PropertyMetadata(null));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ButtonImg), new PropertyMetadata(string.Empty));
public double ImageWidth
{
get { return (double)GetValue(ImageWidthProperty); }
set { SetValue(ImageWidthProperty, value); }
}
public static readonly DependencyProperty ImageWidthProperty =
DependencyProperty.Register("ImageWidth", typeof(double), typeof(ButtonImg), new PropertyMetadata((double)30));
public double ImageHeight
{
get { return (double)GetValue(ImageHeightProperty); }
set { SetValue(ImageHeightProperty, value); }
}
public static readonly DependencyProperty ImageHeightProperty =
DependencyProperty.Register("ImageHeight", typeof(double), typeof(ButtonImg), new PropertyMetadata((double)30));
}
[3.] In this step you must create Template for your new custom control. So you must edit following file "/Themes/Generic.xaml":
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfButtonImg">
<Style TargetType="{x:Type local:ButtonImg}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ButtonImg}">
<Button>
<Button.Content>
<StackPanel Orientation="Horizontal">
<Image Source="{TemplateBinding ImageSource}"
Height="{TemplateBinding ImageHeight}" Width="{TemplateBinding ImageWidth}"
Stretch="Uniform" />
<TextBlock Text="{TemplateBinding Text}" Margin="10,0,0,0" VerticalAlignment="Center" FontSize="20" />
</StackPanel>
</Button.Content>
</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
[4.] Example of using this new custom control is following:
First you must add appropriate namespace:
xmlns:MyNamespace="clr-namespace:WpfButtonImg"
Now you can use it like this:
<MyNamespace:ButtonImg ImageSource="/Images/plug.png" Text="Click me!" />
Related
I'm doing a CustomControl (button) using generic.xaml and dependency properties.
Here is my generic.xaml code :
<Style TargetType="{x:Type local:FlatButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:FlatButton}">
<Grid MinHeight="50" MaxHeight="50" MinWidth="200" MaxWidth="200">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="{TemplateBinding BackgroundDarker}">
</Grid>
<Grid Grid.Column="1" Background="{TemplateBinding Background}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" FontFamily="Segoe UI" Foreground="White" FontWeight="Bold" />
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My customControl class :
Public Shared Shadows BackgroundProperty As DependencyProperty = DependencyProperty.Register("Background", GetType(SolidColorBrush), GetType(FlatButton))
Public Overloads Property Background As SolidColorBrush
Get
Return CType(GetValue(BackgroundProperty), SolidColorBrush)
End Get
Set(value As SolidColorBrush)
SetValue(BackgroundProperty, value)
End Set
End Property
Public Shared BackgroundDarkerProperty As DependencyProperty = DependencyProperty.Register("BackgroundDarker", GetType(SolidColorBrush), GetType(FlatButton))
Public ReadOnly Property BackgroundDarker As SolidColorBrush
Get
Return Background.Darker
End Get
End Property
And finally how I use my control in a UserControl :
<Grid>
<local:FlatButton Background="Red" />
</Grid>
When I put "Red" in the xaml of my FlatButton, the right part is well colored in Red (in VS and in runtime), but what I want is that the left part colores itself automatically with Darker red (it's an extension which works). But it seems not to be colored. I've no binding error in output.
What am I doing wrong ?
Thanks all.
-----EDIT----- :
Ok, to do that I made a converter which convert the "Background" value to a darker color.
I templateBinded the background of the left grid to "Background" with an instance of my converter.
While a converter will work, this functionality belongs inside the FlatButton control code. Use the Background's PropertyChangedCallback to update BackgroundDarker.
public class FlatButton : Button
{
// Background
public static new DependencyProperty BackgroundProperty = DependencyProperty.Register("Background", typeof(SolidColorBrush), typeof(FlatButton), new PropertyMetadata(OnBackgroundChanged));
public new SolidColorBrush Background { get { return (SolidColorBrush)GetValue(BackgroundProperty); } set { SetValue(BackgroundProperty, value); } }
// BackgroundDarker
public static DependencyProperty BackgroundDarkerProperty = DependencyProperty.Register("BackgroundDarker", typeof(SolidColorBrush), typeof(FlatButton));
public SolidColorBrush BackgroundDarker { get { return (SolidColorBrush)GetValue(BackgroundDarkerProperty); } private set { SetValue(BackgroundDarkerProperty, value); } }
private static void OnBackgroundChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// update BackgroundDarker
var btn = (FlatButton)d;
btn.BackgroundDarker = btn.Background.Darker();
}
static FlatButton()
{
// lookless control, get default style from generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(typeof(FlatButton), new FrameworkPropertyMetadata(typeof(FlatButton)));
}
}
public static class SolidColorBrushExtension
{
public static SolidColorBrush Darker(this SolidColorBrush brush)
{
const double perc = 0.6;
return new SolidColorBrush(Color.FromRgb((byte)(brush.Color.R * perc), (byte)(brush.Color.G * perc), (byte)(brush.Color.B * perc)));
}
}
I am new to custom control creation. I have laid some groundwork for a new custom control based on the Selector class. My understanding was that I should use this class since I needed the control to have an Items collection and the ability to handle selections. I believe that changing the ItemTemplate may have overriden some of this ability because I do not receive the SelectionChanged event at the control level or application level. I would think if I'm right that there is some sort of SelectionRegion XAML tag that I can put the DataTemplate innards into. I have not had luck in finding anything like this. After looking through Google for a while, I am ready to just ask. What am I missing? Below is the ItemTemplate markup. Thanks for any help. Thanks even more if you can tell me why the Text in TextBlock is enclosed in parentheses even though the data isn't.
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding}" Foreground="Black" Background="White" MinHeight="12" MinWidth="50"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
At the request of a commenter, here is the complete XAML for the control so far:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SourceMedicalWPFCustomControlLibrary">
<Style TargetType="{x:Type local:MultiStateSelectionGrid}">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="1">
<TextBlock Text="{Binding Code}" Foreground="Black" Background="White" MinHeight="12" MinWidth="50" Padding="2" ToolTip="{Binding Description}"/>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MultiStateSelectionGrid}">
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,0" Content="{TemplateBinding Content}"/>
<ItemsPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And the anemic code-behind as well:
namespace SourceMedicalWPFCustomControlLibrary
{
public class MultiStateSelectionGridState
{
public Brush Background { get; set; }
public Brush Foreground { get; set; }
public Brush Border { get; set; }
public string Text { get; set; }
public MultiStateSelectionGridState()
{
Background = Brushes.White;
Foreground = Brushes.Black;
Border = Brushes.Black;
Text = String.Empty;
}
};
public class MultiStateSelectionGrid : Selector
{
public static readonly DependencyProperty ContentProperty =
DependencyProperty.Register("Content", typeof(object), typeof(MultiStateSelectionGrid),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public object Content
{
get { return (object)GetValue(ContentProperty); }
set { SetValue(ContentProperty, value); }
}
public static readonly DependencyProperty StatesProperty =
DependencyProperty.Register("States", typeof(List<MultiStateSelectionGridState>), typeof(MultiStateSelectionGrid),
new FrameworkPropertyMetadata(new List<MultiStateSelectionGridState>(),
FrameworkPropertyMetadataOptions.AffectsRender));
public List<MultiStateSelectionGridState> States
{
get { return (List<MultiStateSelectionGridState>)GetValue(StatesProperty); }
set { SetValue(StatesProperty, value); }
}
static MultiStateSelectionGrid()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MultiStateSelectionGrid), new FrameworkPropertyMetadata(typeof(MultiStateSelectionGrid)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += new SelectionChangedEventHandler(MultiStateSelectionGrid_SelectionChanged);
}
void MultiStateSelectionGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
MessageBox.Show("Hi");
}
}
}
here is what I do. I use the apply template function of the custom control and add a handlerto the selection chnaged event of the control I want.
simple sample here:
public event EventHandler<SelectionChangedEventArgs> YourControlSelectionChanged;
private void Selector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (ListSelectionChanged != null) {
ListSelectionChanged(sender, e);
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//find or declare your control here, the x:name in xaml should be YourControl
YourControl== this.Template.FindName("YourControl", this) as YourControlType
YourControl.SelectionChanged += ResultListBox_SelectionChanged;
}
you can then bind to the name of the public event (YourControlSelectionChanged) you declared in your custom control class in xaml.
hope this helps.
From reading some full code examples of different controls, I believe my answer is that I am doing this all wrong. Instead, I need to have control that has a Selector like a ListBox in the ControlTemplate. THEN, #JKing 's advice would help me get to where I need to be. The answer to the actual question asked though is the aforementioned change from using Selector as a base class to having a selector in the template for the control. Thanks for the help.
I am not sure what I am doing wrong here. I spent a good hour last night to figure it out, maybe I am just dumb.
I created this user control to display a bordered text, which uses data binding to fill the style and the text.
This is how I call it from the main page:
<mynamespace:BorderedText x:Name="DateTime"
Grid.Column="1"
Grid.Row="0"
BorderStyle="{StaticResource borderStyle}"
LabelStyle="{StaticResource labelStyle}"
TextStyle="{StaticResource valueStyle}"
Label="Current Date/Time"
Text="N/A" />
The control is pretty simple:
<UserControl x:Class="MyNamespace.BorderedText"
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"
d:DesignHeight="480"
d:DesignWidth="480">
<Grid>
<Border Name="border" Style="{Binding BorderStyle}">
<StackPanel>
<TextBlock Style="{Binding LabelStyle}"
Text="{Binding Label}" />
<TextBlock Style="{Binding TextStyle}"
Text="{Binding Text}" />
</StackPanel>
</Border>
</Grid>
The problem is that all data binding works, except for the Border data binding. I also tried to data bind the background or any other property, without success.
Code behind has the DependencyProperty properties set up and thatโs it. Note that the DataContext for data binding is set up in the constructor. Tried to assign it to the Grid or to the Border itself, without success.
Does anybody have any clue or see something big I am missing here?
namespace MyNamespace
{
public partial class BorderedText : UserControl
{
public static readonly DependencyProperty LabelProperty = DependencyProperty.Register("Label", typeof(string), typeof(BorderedText), new PropertyMetadata(null));
public static readonly DependencyProperty LabelStyleProperty = DependencyProperty.Register("LabelStyle", typeof(Style), typeof(BorderedText), new PropertyMetadata(null));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(BorderedText), new PropertyMetadata(null));
public static readonly DependencyProperty TextStyleProperty = DependencyProperty.Register("TextStyle", typeof(Style), typeof(BorderedText), new PropertyMetadata(null));
public static readonly DependencyProperty BorderStyleProperty = DependencyProperty.Register("BorderStyle", typeof(Style), typeof(BorderedText), new PropertyMetadata(null));
public BorderedText()
{
InitializeComponent();
((Grid)this.Content).DataContext = this;
//((Border)this.Content).DataContext = this;
}
public string Label
{
set { SetValue(LabelProperty, value); }
get { return (string)GetValue(LabelProperty); }
}
public Style LabelStyle
{
set { SetValue(LabelStyleProperty, value); }
get { return (Style)GetValue(LabelStyleProperty); }
}
public string Text
{
set { SetValue(TextProperty, value); }
get { return (string)GetValue(TextProperty); }
}
public Style TextStyle
{
set { SetValue(TextStyleProperty, value); }
get { return (Style)GetValue(TextStyleProperty); }
}
public Style BorderStyle
{
set { SetValue(BorderStyleProperty, value); }
get { return (Style)GetValue(BorderStyleProperty); }
}
}
}
---- UPDATE:
It turned out to be something completely different and unrelated to databinding which is properly wired...
In the borderStyle I was using this syntax for a background property:
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush>
<SolidColorBrush.Color>
<Color>
<Color.A>
100
</Color.A>
<Color.R>#95</Color.R>
<Color.B>#ED</Color.B>
</Color>
</SolidColorBrush.Color>
</SolidColorBrush>
</Setter.Value>
</Setter>
which apparently works in the designer but not in the phone.
Changing it to:
<Setter Property="Background">
<Setter.Value>
<SolidColorBrush Color="#649500ED" />
</Setter.Value>
</Setter>
Solved the problem
Well, you forgot one thing... the DataContext of the Border!
Give your UserControl a name, and then you can add to your binding something like:
<TextBox Text="{Binding Path=MyText, ElementName=UserControlRoot}" />
this will work (at least it worked for me in WPF, heh)
I am trying to set up a custom style for my newly made usercontrol, however i am getting the error : "Cannot convert the value in attribute 'Property' to object of type 'System.Windows.DependencyProperty'."
I thought i had set up Dependency properties but it seemed this was not the case, so i did some research and added:
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(BitmapSource), typeof(Image));
to make this:
-- MyButton.Xaml.Cs --
namespace Client.Usercontrols
{
public partial class MyButton : UserControl
{
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(BitmapSource), typeof(Image));
public MyButton()
{
InitializeComponent();
}
public event RoutedEventHandler Click;
void onButtonClick(object sender, RoutedEventArgs e)
{
if (this.Click != null)
this.Click(this, e);
}
BitmapSource _imageSource;
public BitmapSource ImageSource
{
get { return _imageSource; }
set
{
_imageSource = value;
tehImage.Source = _imageSource;
}
}
}
}
This unfortunately does not work. I also tried this:
public BitmapSource ImageSource
{
get { return (BitmapSource)GetValue(MyButton.ImageSourceProperty); }
set
{
SetValue(ImageSourceProperty, value);
}
}
But that did not work and the image was not shown and generated the same error as mentioned previously anyway.
Any ideas?
Regards Kohan.
-- MyButton.Xaml --
<UserControl x:Class="Client.Usercontrols.MyButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" MinHeight="30" MinWidth="40"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Button Width="Auto" HorizontalAlignment="Center" Click="onButtonClick">
<Border CornerRadius="5" BorderThickness="1" BorderBrush="Transparent" >
<Grid>
<Image Name="tehImage" Source="{Binding ImageSource}" />
<TextBlock Name="tehText" Text="{Binding Text}" Style="{DynamicResource ButtonText}" />
</Grid>
</Border>
</Button>
</UserControl>
-- MYButton Style --
<Style TargetType="{x:Type my:MyButton}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type my:MyButton}">
<ContentPresenter />
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="ImageSource" Value="../Images/Disabled.png" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Biggest problem I see is that you're registering the property as owned by Image rather than by your UserControl. Change to:
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource", typeof(BitmapSource), typeof(MyButton));
If that doesn't work, will need to see your XAML.
The standard form for a dependency property is (i've added in your information):
public BitmapSource ImageSource
{
get { return (BitmapSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
/* Using a DependencyProperty as the backing store for ImageSource.
This enables animation, styling, binding, etc... */
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource",
typeof(BitmapSource),
typeof(MyButton),
new UIPropertyMetadata(null)
);
it seems like your also trying to pass through the dependency property to the ImageSource of the object called "tehImage". You can set this up to automatically update using the PropertyChangedCallback... this means that whenever the property is updated, this will call the update automatically.
thus the property code becomes:
public BitmapSource ImageSource
{
get { return (BitmapSource)GetValue(ImageSourceProperty); }
set { SetValue(ImageSourceProperty, value); }
}
/* Using a DependencyProperty as the backing store for ImageSource.
This enables animation, styling, binding, etc... */
public static readonly DependencyProperty ImageSourceProperty =
DependencyProperty.Register("ImageSource",
typeof(BitmapSource), typeof(MyButton),
new UIPropertyMetadata(null,
ImageSource_PropertyChanged
)
);
private static void ImageSource_PropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
{
((MyButton)source).tehImage.ImageSource = (ImageSource)e.NewValue
}
Hopefully with the correctly registered dependency property, this will help you narrow down the issue (or even fix it)
Set the DataContext for your UserControl:
public MyButton()
{
InitializeComponent();
DataContext = this;
}
Alternatively, if you can't do that (since the DataContext is set to another object, for example), you can do this in your XAML:
<UserControl x:Class="Client.Usercontrols.MyButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" MinHeight="30" MinWidth="40"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
x:Name="MyControl">
<Button Width="Auto" HorizontalAlignment="Center" Click="onButtonClick">
<Border CornerRadius="5" BorderThickness="1" BorderBrush="Transparent" >
<Grid>
<Image Name="tehImage" Source="{Binding ElementName=MyControl, Path=ImageSource}" />
<TextBlock Name="tehText" Text="{Binding ElementName=MyControl, Path=Text}" Style="{DynamicResource ButtonText}" />
</Grid>
</Border>
</Button>
</UserControl>
The correct way of implementing a source for an Image in a user control in my opinion is not BitmapSouce. The easiest and best way (according to me again) is using Uri.
Change your dependency property to this (while also defining a change callback event):
ImageSourceProperty = DependencyProperty.Register(
"ImageSource", typeof (Uri), typeof (MyButton),
new FrameworkPropertyMetadata(new PropertyChangedCallback(OnImageSourceChanged)));
and the property to this:
public Uri ImageSource
{
get
{
return (Uri)GetValue(ImageSourceProperty);
}
set
{
SetValue(ImageSourceProperty, value);
}
}
Where your call back is like this:
private static void OnImageSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
MyButton hsb = (MyButton)sender;
Image image = hsb.tehImage;
image.Source = new BitmapImage((Uri) e.NewValue);
}
I am wondering, which is the best and quickest way to get the well known Label Input [or output, doesn't matter] combination in WPF. Its a simple Task, just think of a quick output of the "object" ME:
Name - Christian
Age - 28
Mood - Good
I know, I can use a Grid with TextBlocks. But to be honest, the "short" XAML for this is nearly half a page long (RowDefinitions, ColDefs, Grid.Col on each Label)
The alternative way, using three StackPanels (horizontal) with one vertical seems also a little bit stupid. In this case, I have to give each Label a fixed width, to get the indent correct. And it just does not "feel" right.
So, given the Situation above, you got a custom object with 3-6 Properties you just want to dump as readonly to your GUI, how would you do it (in WPF, Silverlight too, if you are really in the mood :).
I can, of course, write a usercontrol for this. But why reinvent the wheel, if it might be already there...
And finally, to illustrate even further, the example I just created in real life and was the reason for this post:
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Log Count" Width="100"/>
<TextBlock Text="{Binding LastLogRun.LogMessageCount}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Start Time" Width="100"/>
<TextBlock Text="{Binding LastLogRun.StartTime}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="End Time" Width="100"/>
<TextBlock Text="{Binding LastLogRun.EndTime}"/>
</StackPanel>
</StackPanel>
You could use shared size groups to get the auto-sizing Grid behavior of two nicely-lined-up columns, while still being able to pull out the complexity into a UserControl.
Here's an example of using a LabeledEdit control that would do what you're looking for. The complexity has all been factored away into the UserControl, and all you need to do is remember to set Grid.IsSharedSizeScope on the StackPanel:
<Window x:Class="WpfApplication5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication5"
Name="Self" Title="Window1" Height="300" Width="300">
<StackPanel Grid.IsSharedSizeScope="True">
<local:LabeledEdit Label="Name"/>
<local:LabeledEdit Label="Age" Text="28"/>
<!-- and with databinding... -->
<local:LabeledEdit Label="Width"
Text="{Binding Width, ElementName=Self}"/>
<local:LabeledEdit Label="Height"
Text="{Binding Height, ElementName=Self}"/>
</StackPanel>
</Window>
And here's the source code for the UserControl. LabeledEdit.xaml:
<UserControl x:Class="WpfApplication5.LabeledEdit"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Self">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LabeledEdit_Labels"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Content="{Binding Label, ElementName=Self}"/>
<TextBox Grid.Column="1" Text="{Binding Text, ElementName=Self}"/>
</Grid>
</UserControl>
LabeledEdit.xaml.cs:
using System.Windows;
namespace WpfApplication5
{
public partial class LabeledEdit
{
public static readonly DependencyProperty LabelProperty =
DependencyProperty.Register("Label", typeof(object), typeof(LabeledEdit));
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(LabeledEdit),
new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public LabeledEdit()
{
InitializeComponent();
}
public object Label
{
get { return GetValue(LabelProperty); }
set { SetValue(LabelProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
}
}
If you're using 3.5sp1 you can use StringFormat in the binding. Something like this should work...
<TextBlock Text="{Binding LastLogRun.LogMessageCount, StringFormat={}Log Count - {0}}" />
Perhaps you should rethink your UI. Why would you want Label - Textbox on the same line? That's a horrendous waste of space.
Why not Label over texbox? Then you've got a simple UI and simple XAML:
<StackPanel Orientation="Vertical">
<TextBlock>Name</TextBlock>
<TextBox />
<TextBlock>Age</TextBlock>
<TextBox />
<TextBlock>Mood</TextBlock>
<TextBox />
</StackPanel>
Add some styling for your TextBlocks and you've got a nice, clean UI, with very little repetition.
The silverlight toolkit has a DataForm control that works pretty cool!
I know this is 13! years later, but, if anyone else is curious, you can use BulletDecorator (docs). There's special handling for vertical alignment based on the first line of text, if the content is text-based content.
OP's example, written with BulletDecorators:
<StackPanel>
<BulletDecorator>
<BulletDecorator.Bullet>
<TextBlock Text="Log Count" Width="100"/>
</BulletDecorator.Bullet>
<TextBlock Text="{Binding LastLogRun.LogMessageCount}"/>
</BulletDecorator>
<BulletDecorator>
<BulletDecorator.Bullet>
<TextBlock Text="Start Time" Width="100"/>
</BulletDecorator.Bullet>
<TextBlock Text="{Binding LastLogRun.StartTime}"/>
</BulletDecorator>
<BulletDecorator>
<BulletDecorator.Bullet>
<TextBlock Text="End Time" Width="100"/>
</BulletDecorator.Bullet>
<TextBlock Text="{Binding LastLogRun.EndTime}"/>
</BulletDecorator>
</StackPanel>
If you're not a fan of that ๐ syntax (it's a bit verbose for my sake), you can use some attached properties to clean it up some.
BulletHelper.cs
public static class BulletHelper
{
#region Bullet
public static readonly DependencyProperty BulletProperty = DependencyProperty.RegisterAttached(
"Bullet",
typeof(object),
typeof(BulletHelper),
new FrameworkPropertyMetadata(OnBulletChanged)
);
public static object? GetBullet(DependencyObject target)
=> target.GetValue(BulletHelper.BulletProperty);
public static void SetBullet(DependencyObject target, object? value)
=> target.SetValue(BulletHelper.BulletProperty, value);
private static void OnBulletChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is BulletDecorator bulletDecorator)
{
bulletDecorator.Bullet = CreateUiElement(e.NewValue);
}
}
#endregion Bullet
#region Child
public static readonly DependencyProperty ChildProperty = DependencyProperty.RegisterAttached(
"Child",
typeof(object),
typeof(BulletHelper),
new FrameworkPropertyMetadata(OnChildChanged)
);
public static object? GetChild(DependencyObject target)
=> target.GetValue(BulletHelper.ChildProperty);
public static void SetChild(DependencyObject target, object? value)
=> target.SetValue(BulletHelper.ChildProperty, value);
private static void OnChildChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is BulletDecorator bulletDecorator)
{
bulletDecorator.Child = CreateUiElement(e.NewValue);
}
}
#endregion Child
[return: NotNullIfNotNull("value")]
private static UIElement? CreateUiElement(this object? value)
{
return value switch
{
null => null,
// Uncomment if using MaterialDesignThemes (https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit)
// PackIconKind kind => new PackIcon { Kind = kind },
// Uncomment if using FontAwesome6.Svg (https://github.com/MartinTopfstedt/FontAwesome6)
// EFontAwesomeIcon.None => null,
// EFontAwesomeIcon fontAwesomeIcon => new SvgAwesome { Icon = fontAwesomeIcon },
UIElement uiElement => uiElement,
_ => new ContentPresenter { Content = value },
};
}
}
The above attached properties reduces it to:
<StackPanel>
<BulletDecorator BulletHelper.Bullet="Log Count"
BulletHelper.Child="{Binding LastLogRun.LogMessageCount}" />
<BulletDecorator BulletHelper.Bullet="Start Time"
BulletHelper.Child="{Binding LastLogRun.StartTime}" />
<BulletDecorator BulletHelper.Bullet="End Time"
BulletHelper.Child="{Binding LastLogRun.EndTime}" />
</StackPanel>
Unfortunately, you lose out on the Width property that was previously set on the TextBlock.
I wish BulletDecorator was easier to work with. Unfortunately, its two main properties, Child and Bullet, are not dependency properties, and they both must be UIElement.
However. You can make a Control (not a UserControl!) that makes BulletDecorator easier to use.
BulletControl.cs
public class BulletControl : ContentControl
{
static BulletControl()
{
FocusableProperty.OverrideMetadata(typeof(Control), new FrameworkPropertyMetadata(false));
DefaultStyleKeyProperty.OverrideMetadata(typeof(BulletControl), new FrameworkPropertyMetadata(typeof(BulletControl)));
}
#region Bullet
public static readonly DependencyProperty BulletProperty = DependencyProperty.Register(
nameof(Bullet),
typeof(object),
typeof(BulletControl),
new FrameworkPropertyMetadata(BulletChangedCallback)
);
public object? Bullet
{
get => this.GetValue(BulletProperty);
set => this.SetValue(BulletProperty, value);
}
private static void BulletChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is Bullet ctrl)
{
ctrl.SetValue(HasBulletPropertyKey, e.NewValue is not null);
}
}
#endregion Bullet
#region BulletStringFormat
public static readonly DependencyProperty BulletStringFormatProperty = DependencyProperty.Register(
nameof(BulletStringFormat),
typeof(string),
typeof(BulletControl),
new FrameworkPropertyMetadata()
);
public string? BulletStringFormat
{
get => (string?)this.GetValue(BulletStringFormatProperty);
set => this.SetValue(BulletStringFormatProperty, value);
}
#endregion BulletStringFormat
#region BulletTemplate
public static readonly DependencyProperty BulletTemplateProperty = DependencyProperty.Register(
nameof(BulletTemplate),
typeof(DataTemplate),
typeof(BulletControl),
new FrameworkPropertyMetadata()
);
public DataTemplate? BulletTemplate
{
get => (DataTemplate?)this.GetValue(BulletTemplateProperty);
set => this.SetValue(BulletTemplateProperty, value);
}
#endregion BulletTemplate
#region BulletTemplateSelector
public static readonly DependencyProperty BulletTemplateSelectorProperty = DependencyProperty.Register(
nameof(BulletTemplateSelector),
typeof(DataTemplateSelector),
typeof(BulletControl),
new FrameworkPropertyMetadata()
);
public DataTemplateSelector? BulletTemplateSelector
{
get => (DataTemplateSelector?)this.GetValue(BulletTemplateSelectorProperty);
set => this.SetValue(BulletTemplateSelectorProperty, value);
}
#endregion BulletTemplateSelector
#region HasBullet
private static readonly DependencyPropertyKey HasBulletPropertyKey = DependencyProperty.RegisterReadOnly(
name: nameof(HasBullet),
propertyType: typeof(bool),
ownerType: typeof(BulletControl),
typeMetadata: new FrameworkPropertyMetadata()
);
public bool HasBullet
{
get => (bool)this.GetValue(HasBulletPropertyKey.DependencyProperty);
private set => this.SetValue(HasBulletPropertyKey, value);
}
#endregion HasBullet
}
Generic.xaml
<Style TargetType="{x:Type controls:BulletControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:BulletControl}">
<BulletDecorator>
<BulletDecorator.Bullet>
<ContentPresenter ContentSource="Bullet" />
</BulletDecorator.Bullet>
<ContentPresenter ContentSource="Content" />
</BulletDecorator>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
With ๐, the OP's sample code becomes:
<StackPanel>
<StackPanel.Resources>
<DataTemplate x:Key="BulletTemplate">
<TextBlock Text="{Binding}"
Width="100" />
</DataTemplate>
</StackPanel.Resources>
<BulletControl Bullet="Log Count"
Content="{Binding LastLogRun.LogMessageCount}"
BulletTemplate="{StaticResource BulletTemplate}"
/>
<BulletControl Bullet="Start Time"
Content="{Binding LastLogRun.StartTime}"
BulletTemplate="{StaticResource BulletTemplate}"
/>
<BulletControl Bullet="End Time"
Content="{Binding LastLogRun.EndTime}"
BulletTemplate="{StaticResource BulletTemplate}"
/>
</StackPanel>