I am trying to create in WPF a user control that is a simple view, without view model.
I have a label and a textBox inside.
ControlTextBox.xaml :
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Name="LabelColumn"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Label Grid.Column="0" Name="LabelControl"></Label>
<TextBox Grid.Column="1"
Name="TextBox"
Width="Auto"
HorizontalAlignment="Stretch"
Height="22"></TextBox>
</Grid>
ControlTextBox.xaml.cs :
public partial class ControlTextBox : UserControl
{
public ControlTextBox ()
{
InitializeComponent();
}
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(ControlTextBox),
new PropertyMetadata(default(string), new PropertyChangedCallback(OnTextChanged)));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
private static void OnTextChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
ControlTextBox controlTextBox = sender as ControlTextBox;
controlTextBox.UpdateText(args);
}
private void UpdateText(DependencyPropertyChangedEventArgs args)
{
TextBox.Text = (string)args.NewValue;
}
}
Where the control is used :
<controls:ControlTextBox Text="{Binding PersonModel.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>
But this way, where I am using the control the binding is done in a single way. If the property from binding is changed I can see it in the textBox.
But if I write something new in the textBox, the property from binding is not changed.
What should I do to make also the change from textBox to the binding property?
Use a RelativeSource Binding in ControlTextBox.xaml:
<TextBox ...
Text="{Binding Text, RelativeSource={RelativeSource AncestorType=UserControl}}" />
and drop the PropertyChangedCallback:
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register(nameof(Text), typeof(string), typeof(ControlTextBox));
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
Related
I have created reusable components let's say a label and a textbox:
HeaderAndTextBox.xaml
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock
Margin="10,0,0,0"
FontSize="16"
FontWeight="DemiBold"
Foreground="White"
Text="{Binding Header, ElementName=root}" />
<TextBox
Grid.Row="1"
MaxWidth="300"
Margin="10"
mah:TextBoxHelper.ClearTextButton="True"
mah:TextBoxHelper.IsWaitingForData="True"
FontSize="16"
Text="{Binding TextBoxContent, ElementName=root}" />
</Grid>
Now as you can see I created dependency properties for the Text properties. Here is the code behind:
HeaderAndTextBox.xaml.cs
public partial class HeaderAndTextBox : UserControl
{
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(string), typeof(HeaderAndTextBox), new PropertyMetadata(string.Empty));
public string Header
{
get { return (string)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty TextBoxContentProperty =
DependencyProperty.Register("TextBoxContent", typeof(string), typeof(HeaderAndTextBox), new PropertyMetadata(string.Empty));
public string TextBoxContent
{
get { return (string)GetValue(TextBoxContentProperty); }
set { SetValue(TextBoxContentProperty, value); }
}
public HeaderAndTextBox()
{
InitializeComponent();
}
}
In my view I use this reusable component like this:
MyView.xaml
<controls:HeaderAndTextBox
Grid.Row="1"
Margin="10,10,0,0"
Header="Last Name"
TextBoxContent="{Binding Path=LastName, UpdateSourceTrigger=PropertyChanged}" />
And my view model:
MyViewModel.cs
private string? _lastName;
public string? LastName
{
get
{
return _lastName;
}
set
{
_lastName = value;
OnPropertyChanged(nameof(LastName));
}
}
Question is, how can I bind this dependency property to my view model's property? As my approach doesn't work. I have more than one property so I must find a solution for the binding to be dynamic.
Could it be that for this kind of problem, I should use a completely different approach?
The internal elements must bind to the control's properties either by Binding.ElementName, where the the named UserControl is the binding source or by using Binding.RelativeSource.
HeaderAndTextBox.xaml
<UserControl>
<TextBox Text="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=TextBoxContent, UpdateSourceTrigger=PropertyChanged}" />
</UserControl>
Next, make sure the DataContext of the parent element that hosts HeaderAndTextBox is correct:
MainWindow.xaml
<Window>
<Window.DataContext>
<MyViewModel />
</Window.DataContext>
<StackPanel>
<!-- The HeaderAndTextBox inherits the parent's DataContext,
which is MyViewModel, automatically. -->
<HeaderAndTextBox TextBoxContent="{Binding SomeMyViewModelTextProperty}" />
<Grid DataContext="{Binding GridViewModel}">
<!-- Same control, different instance,
binds to a different view model class (GridViewModel). -->
<HeaderAndTextBox TextBoxContent="{Binding SomeGridViewModelTextProperty}" />
</Grid>
</StackPanel>
</Window>
To make the HeaderAndTextBox.TextBoxContent property send data back to the view model automatically (when typing into the TextBox), you should configure the dependency property accordingly by using a FrameworkPropertyMetadata object instead of a PropertyMetadata and set the FrameworkPropertyMetadata.BindsTwoWayByDefault property:
HeaderAndTextBox.xaml.cs
partial class HeaderAndTextBox : UserControl
{
public static readonly DependencyProperty TextBoxContentProperty = DependencyProperty.Register(
"TextBoxContent",
typeof(string),
typeof(HeaderAndTextBox),
new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public string TextBoxContent
{
get => (string)GetValue(TextBoxContentProperty);
set => SetValue(TextBoxContentProperty, value);
}
}
I'm new to WPF and I'm try to bind a dependacy property.
I want that the the text I write on the WPFCtrl:FilterTextBox will be displaied in the TextBlock
here is my XAML
xmlns:WPFCtrl="clr-namespace:WPFControls"
xmlns:local="clr-namespace:WpfApplication9"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:Person x:Key="myDataSource" />
</Window.Resources>
<Grid>
<StackPanel>
<StackPanel.DataContext>
<Binding Source="{StaticResource myDataSource}"/>
</StackPanel.DataContext>
<WPFCtrl:FilterTextBox Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged }"/>
<TextBlock Width="55" Height="25" Text="{Binding Path=Name, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
</Grid>
here the Person Class
namespace WpfApplication9
{
public class Person : INotifyPropertyChanged
{
private string name = "";
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
public Person()
{
}
public Person(string value)
{
this.name = value;
}
public string Name
{
get { return name; }
set
{
name = value;
// Call OnPropertyChanged whenever the property is updated
OnPropertyChanged("Name");
}
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
and the FilterTextBox Text Property
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(FilterTextBox), new PropertyMetadata());
public string Text
{
//get { return _tbFilterTextBox.Text == null ? null : _tbFilterTextBox.Text.TrimEnd(); }
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
//set { _tbFilterTextBox.Text = value; }
}
The problem is that it does not enter in OnPropertyChanged()
What am I doing wrong?
does this "FilterTextBox" Control update the DP everytime text is inserted?
i guess the FilterTextBox has a ControlTemplate with a regular TextBox inside.
something like
<ControlTemplate TargetType="{x:Type FilterTextBox}">
<TextBox Name="PART_FilterTextBoxInputField" Text="{TemplateBinding Text}"/>
</ControlTemplate>
you need to set the binding where the internal textbox binds to your Text-Dependcy Property to use UpdateSourceTrigger=PropertyChanged too. otherwise the binding will only update when the textbox loses focus.
The problem is that the TextProperty in FilterTextBox doesn't bind TwoWay by default.
Either set the BindingMode to TwoWay
<WPFCtrl:FilterTextBox Text="{Binding Path=Name,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged }"/>
Or change the metadata for the DependencyProperty Text so it binds twoway by default
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text",
typeof(string),
typeof(FilterTextBox),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
here's the code. All it does is, it creates a new ListBox control inside a StackPanel and adds to it a new Item. The problem is described at the bottom of the page.
MyWindow.xaml
<Window.Resources>
<DataTemplate x:Key="itemsTemplate">
<Border Width="200" Height="50" >
<ListBox ItemsSource="{Binding Title}" />
</Border>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<ItemsControl Grid.Column="1"
Grid.Row="0"
ItemsSource="{Binding ElementName=mainWindow, Path=DataItems}"
ItemTemplate="{StaticResource itemsTemplate}">
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
MyWindow.xaml.cs
private ObservableCollection<MyDataItem> dataItems;
public ObservableCollection<MyDataItem> DataItems
{
get { return dataItems; }
}
public Window1()
{
dataItems = new ObservableCollection<MyDataItem>();
InitializeComponent();
}
MyDataItem.cs
public class MyDataItem : DependencyObject
{
public string Title
{
get
{
return (string)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string),
typeof(MyDataItem), new UIPropertyMetadata(""));
public MyDataItem(string title)
{
Title = title;
}
}
Add a new Listbox with item
void addNewItem_Click(object sender, RoutedEventArgs e)
{
MyDataItem item = new MyDataItem("12");
dataItems.Add(item);
e.Handled = true;
}
after that action, I see that new ListBox control, but all its item's characters are treated as a separate items as You can see # the picture. Why ?
[ EDIT ]
solved, I had to change from string to List everywhere in that class:
public class MyDataItem : DependencyObject
{
public List<string> Title
{
get
{
return (List<string>)GetValue(TitleProperty);
}
set
{
SetValue(TitleProperty, value);
}
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.Register("Title", typeof(string),
typeof(MyDataItem),
new UIPropertyMetadata(new List<string>()));
public MyDataItem(List<string> title)
{
Title = title;
}
}
Because you are giving data source as string.. it is converting that string to collection (character array) since itemssource property will always be collection..
if you still want to create a list box in data template with single item (in you case it is Title).. then try this..
Change you title property to List and while creating your title create a list which contains one string . so item source will be list and since itemssource contains one string it will create only one item.
That's correct, because you declared to instantiate a listbox for each item. Since an item is exposing a string (thus an array of characters), the listbox shows correctly one char for every row.
Why do you insert an ItemsControl (the listbox) inside another ItemsControl?
Cheers
Trying to understand this binding process of the WPF.
See the code at the bottom.
In my "viewmodel", see the code at the bottom, i have an observable collection that is populating the listview with the items. Thats the one that contains a path called symbol to set the selected index in the combobox. Now my problem is that i need to populate the combobox from another list before its added to the listview (some default values).
Since i just started with WPF i thought that perhaps you can use 2 different ObservableCollections in the same class to achieve this but that didn't work. So how can i populate the datatemplate with the default values before they are bound/added to the listview?
This is what i use to populate the listview, see the viewmodelcontacts at the bottom.
I also tried to add another class with a new observablecollection that i could use in my combobox, but i didn't get that to work either.
The data that should be populated comes from a XML file located as a resource in my app.
Another question, is it possible to add commands to images? or are commands only available from controls that inherit from the button_base class? I wanted to use an image next to an element and when the user clicked on that image they would remove the element.
From the answer below, is it possible without adding a button since i don't want the button feeling (for instance when hovering and clicking)*
Window.xaml:
<r:RibbonWindow x:Class="Onyxia_KD.Windows.ContactWorkspace"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:r="clr-namespace:Microsoft.Windows.Controls.Ribbon;assembly=RibbonControlsLibrary"
Title="Contact card" ResizeMode="NoResize" Height="600" Width="600"
Background="White">
<r:RibbonWindow.Resources>
<DataTemplate x:Key="cardDetailFieldTemplate">
<TextBox Text="{Binding Path=Field}" MinWidth="150"></TextBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailValueTemplate">
<TextBox Text="{Binding Path=Value}" MinWidth="150"></TextBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailSymbolTemplate">
<!-- Here is the problem. Populating this with some default values for each entry before the selectedIndex is bound from the datasource -->
<ComboBox SelectedIndex="{Binding Path=Symbol}" DataContext="_vmSettings" ItemsSource="{Binding Symbols}" Grid.Column="1" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Center">
<ListViewItem Padding="0,3,0,3" Content="{Binding Path=Value}" />
</ComboBox>
</DataTemplate>
<DataTemplate x:Key="cardDetailCategoryTemplate">
<ComboBox SelectedIndex="{Binding Path=Category}" Grid.Column="1" Margin="0,0,10,0" VerticalAlignment="Center" HorizontalAlignment="Center">
<!--same as the combobox above but categories instead of symbols-->
</ComboBox>
</DataTemplate>
</r:RibbonWindow.Resources>
...
<ListView ItemsSource="{Binding ContactData}" Foreground="Black" SelectionMode="Single" x:Name="cardDetailList" KeyDown="cardDetailList_KeyDown">
<ListView.View>
<GridView>
<GridViewColumn Header="Category" Width="auto" CellTemplate="{StaticResource cardDetailCategoryTemplate}"></GridViewColumn>
<GridViewColumn Header="Field" Width="auto" CellTemplate="{StaticResource cardDetailFieldTemplate}"></GridViewColumn>
<GridViewColumn Header="Symbol" Width="70" CellTemplate="{StaticResource cardDetailSymbolTemplate}"></GridViewColumn>
<GridViewColumn Header="Value" Width="auto" CellTemplate="{StaticResource cardDetailValueTemplate}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
Code behind:
private ViewModelContacts _vm;
public ContactWorkspace()
{
InitializeComponent();
_vm = new ViewModelContacts();
this.DataContext = _vm;
}
private void Run_MouseUp(object sender, MouseButtonEventArgs e)
{
_vm.AddNewDetail();
}
private void Image_MouseUp(object sender, MouseButtonEventArgs e)
{
_vm.AddNewDetail();
}
private void cardDetailList_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete)
{
if (MessageBox.Show("Are you sure that you want to delete this?", "Warning!", MessageBoxButton.YesNo, MessageBoxImage.Warning) == MessageBoxResult.Yes)
{
_vm.RemoveDetail(cardDetailList.SelectedIndex);
}
}
}
ViewModelContacts:
public ObservableCollection<ContactCardData> ContactData { get; set; }
public ViewModelContacts()
{
ContactData = new ObservableCollection<ContactCardData>();
Populate();
}
private void Populate()
{
ContactData.Add(new ContactCardData("Test", 0, 0, "Value123"));
ContactData.Add(new ContactCardData("Test2", 1, 1, "Value1234"));
ContactData.Add(new ContactCardData("Test3", 2, 2, "Value1235"));
ContactData.Add(new ContactCardData("Test4", 3, 3, "Value12356"));
}
public void UpdateNode()
{
ContactData.ElementAt(0).Value = "Giraff";
}
public void AddNewDetail()
{
ContactData.Add(new ContactCardData());
}
public void RemoveDetail(int position)
{
ContactData.RemoveAt(position);
}
ViewModelContactData:
public class ContactCardData : DependencyObject
{
public int Category
{
get { return (int)GetValue(CategoryProperty); }
set { SetValue(CategoryProperty, value); }
}
// Using a DependencyProperty as the backing store for Category. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CategoryProperty =
DependencyProperty.Register("Category", typeof(int), typeof(ContactCardData), new UIPropertyMetadata(0));
public string Field
{
get { return (string)GetValue(FieldProperty); }
set { SetValue(FieldProperty, value); }
}
// Using a DependencyProperty as the backing store for Field. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FieldProperty =
DependencyProperty.Register("Field", typeof(string), typeof(ContactCardData), new UIPropertyMetadata(""));
public string Value
{
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(ContactCardData), new UIPropertyMetadata(""));
public int Symbol
{
get { return (int)GetValue(SymbolProperty); }
set { SetValue(SymbolProperty, value); }
}
// Using a DependencyProperty as the backing store for Symbol. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SymbolProperty =
DependencyProperty.Register("Symbol", typeof(int), typeof(ContactCardData), new UIPropertyMetadata(0));
public ContactCardData()
{
}
public ContactCardData(string field, int category, int symbol, string value)
{
this.Symbol = symbol;
this.Category = category;
this.Field = field;
this.Value = value;
}
}
Define your Window class as follows (explanation will be later):
<Window x:Class="TestCustomTab.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:TestCustomTab="clr-namespace:TestCustomTab" Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate x:Key="workingTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"/>
<ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding Symbols}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Fill="Red" Height="5" Width="5"/>
<TextBlock Grid.Column="1" Text="{Binding}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
<DataTemplate x:Key="personalTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}"/>
<ComboBox Grid.Column="1" SelectedIndex="0" ItemsSource="{Binding Symbols}">
<ComboBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Fill="Green" Height="5" Width="5"/>
<TextBlock Grid.Column="1" Text="{Binding}" />
</Grid>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</DataTemplate>
<TestCustomTab:ContactDataByTypeTemplateSelector x:Key="contactDataByTypeTemplateSelector"/>
</Window.Resources>
<Grid x:Name="grid">
<ListView ItemsSource="{Binding ContactDataCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TestCustomTab:Window1}}}">
<ListView.View>
<GridView>
<GridViewColumn Header="Column" Width="200" CellTemplateSelector="{StaticResource contactDataByTypeTemplateSelector}">
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Lets assume that your ContactData looks as follows:
public class ContactData : INotifyPropertyChanged
{
private string _name;
private int _homePhone;
private int _mobilePhone;
private string _value;
private ContactDataType _dataType;
public ContactData()
{
}
public ContactData(string name, int homePhone, int mobilePhone, string value, ContactDataType dataType)
{
_name = name;
_dataType = dataType;
_value = value;
_mobilePhone = mobilePhone;
_homePhone = homePhone;
}
#region Implementation of INotifyPropertyChanged
public ContactDataType DataType
{
get { return _dataType; }
set
{
if (_dataType == value) return;
_dataType = value;
raiseOnPropertyChanged("DataType");
}
}
public string Name
{
get { return _name; }
set
{
if (_name == value) return;
_name = value;
raiseOnPropertyChanged("Name");
}
}
public int HomePhone
{
get { return _homePhone; }
set
{
if (_homePhone == value) return;
_homePhone = value;
raiseOnPropertyChanged("HomePhone");
}
}
public int MobilePhone
{
get { return _mobilePhone; }
set
{
if (_mobilePhone == value) return;
_mobilePhone = value;
raiseOnPropertyChanged("MobilePhone");
}
}
public string Value
{
get { return _value; }
set
{
if (_value == value) return;
_value = value;
raiseOnPropertyChanged("Value");
raiseOnPropertyChanged("Symbols");
}
}
public ReadOnlyCollection<char> Symbols
{
get
{
return !string.IsNullOrEmpty(_value) ? new ReadOnlyCollection<char>(_value.ToCharArray()) : new ReadOnlyCollection<char>(new List<char>());
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void raiseOnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
It has DataType property of type ContactDataType which is enum:
public enum ContactDataType
{
Working,
Personal
}
In ability to have different DataTemplates for the same entities differentiated by some feature you need to use DataTemplateSelector. The technique is in inheriting from DataTemplateSelector and overriding SelectTemplate method. In our case:
public class ContactDataByTypeTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var contactData = item as ContactData;
var control = container as FrameworkElement;
if (contactData != null & control != null)
switch (contactData.DataType)
{
case ContactDataType.Working:
return control.TryFindResource("workingTemplate") as DataTemplate;
case ContactDataType.Personal:
return control.TryFindResource("personalTemplate") as DataTemplate;
default:
return base.SelectTemplate(item, container);
}
return base.SelectTemplate(item, container);
}
}
Here is Window1 class in code behind:
public partial class Window1 : Window
{
private ObservableCollection<ContactData> _contactDataCollection = new ObservableCollection<ContactData>();
public Window1()
{
ContactDataCollection.Add(new ContactData("test1", 0, 1, "value1", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test2", 0, 1, "value2", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test3", 0, 1, "value3", ContactDataType.Working));
ContactDataCollection.Add(new ContactData("test4", 0, 1, "value4", ContactDataType.Personal));
ContactDataCollection.Add(new ContactData("test5", 0, 1, "value5", ContactDataType.Personal));
InitializeComponent();
}
public ObservableCollection<ContactData> ContactDataCollection
{
get { return _contactDataCollection; }
}
}
Now explanation:
We created some class that we need to represent to user (ContactData) and let him to have feature - ContactDataType.
We created 2 DataTemplates in resources (x:Key is important) for ContactDataType.Working and ContactDataType.Personal
We created DataTemplateSelector to have ability switch templates by feature.
In our first GridViewColumn we defined CellTemplateSelector and bind to it our ContactDataByTypeTemplateSelector.
In runtime whenever the collection changes ContactDataByTypeTemplateSelector select to us template based on item feature and we may have any number of templates for any number of defined features.
Notice: change TestCustomTab for your namespace.
For the last question, you can use a button and template it to include an image.
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>