Default DataTemplate for a control - c#

I am trying to make a property editor which would show different control for different data types.
For example if the data is a bool it should be a checkbox. If it is a color it should be a color picker. If it is an int it should be a numeric up down ect.
For everything else it should be a textbox <-- this is where im struggling.
for bool I can do
<DataTemplate DataType="{x:Type mscorlib:Boolean}">
<CheckBox IsChecked="{Binding Path=.}"/>
</DataTemplate>
and this works perfectly.
But I can't figure out how to make the default textbox case happen.
<DataTemplate>
<TextBlock >
</DataTemplate>
would give me IDictionary must have a Key attribute error
If I adds a key to the template it won't be used unless I explicitly do something like DataTemplate="..."
I cant seem to find a way for the given template to target more than one type either. Which is forcing me to copy paste the template over and over for each type I wish to support.
Is there a better way to do it??

Most professional way of doing this to use templateselector.
Use the IntegerUpDown control in the xtended wpf toolkit for numeric
up and down
Use the colorpicker control of xtended wpf toolkit for color picker
Include this in xaml
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
<DataTemplate x:Key="TEMPLATE">
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<DataTemplate x:Key="BOOLEANTEMPLATE">
<CheckBox IsChecked="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
<DataTemplate x:Key="numericTemplate">
<xctk:IntegerUpDown Name="myUpDownControl" />
</DataTemplate>
<DataTemplate x:Key="ColorTemplate">
<xctk:ColorPicker></xctk:ColorPicker>
</DataTemplate>
<me:DynamicDataTemplateSelector x:Key="datagridDynamictemplateselector" BooleanTemplate="{StaticResource BOOLEANTEMPLATE}"
ColorTemplate ="{StaticResource ColorTemplate}"
NumericTemplate ="{StaticResource numericTemplate}"
TextBoxTemplate="{StaticResource TEMPLATE}" />
Here is the class which override datatemplateselector class
public class DynamicDataTemplateSelector : DataTemplateSelector
{
public DataTemplate TextBoxTemplate{get;set;}
public DataTemplate BooleanTemplate{get;set;}
public DataTemplate NumericTemplate { get; set; }
public DataTemplate ColorTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
DataTemplate dataTemplate = TextBoxTemplate;
if(item!=null)
{
Type dataTypeOfValue = item.GetType();
if(dataTypeOfValue==typeof(int))
{
dataTemplate = NumericTemplate;
}
else if(dataTypeOfValue==typeof(Color))
{
dataTemplate = ColorTemplate;
}
else if (dataTypeOfValue == typeof(Boolean) || dataTypeOfValue == typeof(bool))
{
dataTemplate = BooleanTemplate;
}
}
return dataTemplate;
}

I solved the problem by using xaml style + converter.
<ContentPresenter Content="{Binding MyValue}"
<ContentPresenter.Style>
<Style TargetType="ContentPresenter">
<Style.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="Dead beef"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style.Setters>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=., Converter={StaticResource ToTypeConverter}}" Value="{x:Type mscorlib:Boolean}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=.}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentPresenter.Style>
</ContentPresenter>
converter
public class ToTypeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null) ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The idea is to put the default template inside style's ContentTemplate and only modify the special ones using style trigger.

Related

Conditional text binding XAML

I have 3 properties that I'm trying to bind to a Textblock in XAML. One is a conditional and the other two are the strings that I want to display depending on that conditional.
<TextBlock Text="{Binding TrueText}" Style="{StaticResource styleSimpleText}" Visibility="{Binding ShowTrueText, Converter={StaticResource boolToVisibilityConverter}}"/>
<TextBlock Text="{Binding FalseText}" Style="{StaticResource styleSimpleText}" Visibility="{Binding ShowTrueText, Converter={StaticResource invertedBoolToVisibilityConverter}}"/>
This works, but now the textblocks have to have different names. Can I turn this into one TextBlock with the conditional inside of it?
You could achieve that with a Style and a DataTrigger:
<TextBlock>
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Text" Value="{Binding FalseText}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ShowTrueText}" Value="True">
<Setter Property="Text" Value="{Binding TrueText}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
An alternative would be to use a MultiBinding with a multi-value converter:
<TextBlock>
<TextBlock.Text>
<MultiBinding Converter="{StaticResource TextConverter}">
<Binding Path="TrueText"/>
<Binding Path="FalseText"/>
<Binding Path="ShowTrueText"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The converter would look like this:
public class TextConverter : IMultiValueConverter
{
public object Convert(
object[] values, Type targetType, object parameter, CultureInfo culture)
{
var trueText = (string)values[0];
var falseText = (string)values[1];
var showTrueText = (bool)values[2];
return showTrueText ? trueText : falseText;
}
public object[] ConvertBack(
object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
The way we do this type of thing for MVVM is to create a property in your viewmodel for this. This allows for you to have unit testing for your condition on the viewmodel.
The property in your viewmodel will be the string value that the TextBlock is bound to. The viewmodel at some point will determine the value of that string based on the conditional logic that you need.
Yes, you can, just wrap them in a TextBlock as follows:
<TextBlock x:name="myTextBlock" Style="{StaticResource styleSimpleText}">
<TextBlock Text="{Binding TrueText}" Visibility="{Binding ShowTrueText, Converter={StaticResource boolToVisibilityConverter}}"/>
<TextBlock Text="{Binding FalseText}" Visibility="{Binding ShowTrueText, Converter={StaticResource invertedBoolToVisibilityConverter}}"/>
</TextBlock>
However, I think the best answer is the one provided by Clemens (using a DataTrigger).
You could set it up in your viewmodel and let it determine which text to show.
private static readonly string TRUETEXT = "This is the text to show when true";
private static readonly string FALSETEXT = "This is the text to show when false";
private bool _myBooleanProperty;
public bool MyBooleanProperty
{
get { return _myBooleanProperty; }
set
{
if (_myBooleanProperty != value)
{
_myBooleanProperty = value;
OnPropertyChanged("MyBooleanProperty");
OnPropertyChanged("ResultText");
}
}
}
public string ResultText
{
get
{
return MyBooleanProperty ? TRUETEXT : FALSETEXT;
}
}
Then you bind to it with just a single textblock. No visibility converter needed.
If there is a state where no text should show, you could work that in as well.
<TextBlock Text="{Binding ResultText}" Style="{StaticResource styleSimpleText}" />
In my opinion, the best solution to this problem would be a new string property in your view model which returns either TrueText or FalseText depending on the conditional. With such a property, you can just use a plain binding.
public string TheNewProperty
{
get
{
return ShowTrueText ? TrueText : FalseText;
}
}
<TextBlock Text="{Binding TheNewProperty}" Style="{StaticResource styleSimpleText}"/>

CheckBox Show/Hide TextBox WPF

As the title says, Iam trying to show/hide a TextBox in WPF without writing code in MainWindow.xaml.cs file.
Model:
public class Person
{
public string Comment { get; set; }
}
View:
<Window x:Class="PiedPiper.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="Window"
Title="WPF" Height="400" Width="400">
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" Visibility="Hidden" Name="CommentTextBox"></TextBox>
</Grid>
ViewModel:
public class PersonViewModel : INotifyPropertyChanged
{
public PersonViewModel(Person person)
{
Comment = person.Comment;
}
private string _comment;
public string Comment
{
get { return _comment; }
set { _comment = value; OnPropertyChanged("Comment"); }
}
private void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
So the TextBox should be hidden at start, but visible when checkbox is checked. Please help!
Thanks.
You can bind TextBox.Visiblity to CheckBox.IsChecked. If you want to toggle between Hidden and Visible then you need to either write custom IValueConverter or create simple Style.Trigger
<StackPanel>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}" Name="CommentTextBox">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=CommentCheckBox, Path=IsChecked}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</StackPanel>
if you want to toggle between Collapsed and Visible there is an easier way and you can use build in BooleanToVisibilityConverter
<StackPanel>
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</StackPanel.Resources>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=CommentCheckBox, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}"
Name="CommentTextBox"/>
</StackPanel>
The simplest way is to write a custom "BooleanToHiddenVisibilityConverter" and use it (like dkozl said).
It's a really simple converter and it comes in handy in many situations. I think that every descent WPF application should have one.
public sealed class BooleanToHiddenVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool bValue = false;
if (value is bool)
{
bValue = (bool)value;
}
return (bValue) ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is Visibility)
{
return (Visibility)value == Visibility.Visible;
}
return false;
}
}
And use it like dkozl said:
<StackPanel>
<StackPanel.Resources>
<BooleanToHiddenVisibilityConverter x:Key="BooleanToHiddenVisibilityConverter"/>
</StackPanel.Resources>
<CheckBox Content="Show comment" Name="CommentCheckBox"/>
<TextBox
Text="{Binding Comment, UpdateSourceTrigger=PropertyChanged}"
Visibility="{Binding ElementName=CommentCheckBox, Path=IsChecked,
Converter={StaticResource BooleanToHiddenVisibilityConverter}}"
Name="CommentTextBox"/>
</StackPanel>

Binding the checked state of a togglebutton to the visibility of a usercontrol

I have a way of doing this now using code-behind and changing visibility of 'panels' , but I wonder if this can be done in a straight xaml way?
You should be able to bind this using ElementName, along with an IValueConverter that converts true/false to Visibility:
<Grid>
<Grid.Resources>
<local:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
</Grid.Resources>
<UserControl Visibility="{Binding ElementName=toggle,
Path=IsChecked,
Converter={StaticResource BoolToVisibilityConverter}}"
/>
<ToggleButton x:Name="toggle" />
</Grid>
And the converter:
public class BoolToVisibilityConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var isChecked = (bool)value;
return isChecked ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Here you can do it like below.. you can have both your usercontrol and togglebutton as the content of a parent Contentcontrol and use DataTemplate triggers to set visibility of user control depending on checked status of ToggleButton
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<local:myusercontrol x:Name="control"/>
<ToggleButton Content="click" x:Name="toggleBtn"/>
</StackPanel>
<DataTemplate.Triggers>
<Trigger Property="IsChecked" Value="false" SourceName="toggleBtn">
<Setter Property="Visibility" Value="Visible" TargetName="control"/>
</Trigger>
<Trigger Property="IsChecked" Value="true" SourceName="toggleBtn">
<Setter Property="Visibility" Value="Collapsed" TargetName="control"/>
</Trigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>

listbox isSelected databinding in DataTemplate

I try to simply databind IsSelected property with IsSelected field in my class. But after I change the value in code, it doesn't change the property, neither does clicking on ListBoxItem change the field value.
XAML:
<FlipView ItemsSource="{Binding Source={StaticResource itemsViewSource}}" ... >
<FlipView.ItemTemplate>
<DataTemplate>
<UserControl Loaded="StartLayoutUpdates"
Unloaded="StopLayoutUpdates">
<!-- other controls -->
<ListBox Grid.Row="1" Grid.ColumnSpan="3"
SelectionMode="Multiple" VerticalAlignment="Center"
ItemsSource="{Binding Answers}">
<ListBox.Resources>
<local:LogicToText x:Key="logToText" />
</ListBox.Resources>
<!-- bind IsSelected only in one way from
code to content -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<ListBoxItem
IsSelected="{Binding IsSelected, Mode=TwoWay, Converter={StaticResource logToText}}"
Content="{Binding IsSelected, Mode=TwoWay, Converter={StaticResource logToText}}">
</ListBoxItem>
</DataTemplate>
</ItemsControl.ItemTemplate>
<!-- not working at all
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected"
Value="{Binding IsSelected, Mode=TwoWay}"/>
<Setter Property="Content"
Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>-->
</ListBox>
</UserControl>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Code:
Answers
private ObservableCollection<PrawoJazdyDataAnswer> _answers =
new ObservableCollection<PrawoJazdyDataAnswer>();
public ObservableCollection<PrawoJazdyDataAnswer> Answers
{
get
{
return this._answers;
}
}
Single item(Answer)
public class PrawoJazdyDataAnswer : NPCHelper// PrawoJazdy.Common.BindableBase
{
public PrawoJazdyDataAnswer(String ans, bool ansb)
{
this._ans = ans;
this._isSelected = ansb;
}
public override string ToString()
{
return _isSelected.ToString(); //Only For debug purposes
//normally return _ans
}
private string _ans;
public string Ans
{
get { return this._ans; }
//set { this.SetProperty(ref this._ans, value); }
}
private bool _isSelected;
public bool IsSelected
{
get { return this._isSelected; }
set
{
_isSelected = value;
FirePropertyChanged("IsSelected");
//this.SetProperty(ref this._isSelected, value);
}
}
}
FirePropertyChanged
public class NPCHelper : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void FirePropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
}
Converter(which sometimes seems to be needed and others not..., I tried ~10 approaches from different tutorials/examples)
public class LogicToText : IValueConverter
{
/// <summary>
///
/// </summary>
public object Convert(object value, Type targetType,
object parameter, string language)
{
//if (value == null || (bool)value == false)
// return "False";
return value.ToString();
}
/// <summary>
///
/// </summary>
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
return value.ToString().Contains("True") ? true : false;
}
Thanks in advance, and sorry for my English(still learning).
#edit
Thanks for quick reply.
For test purposes I created a button and text block:
<Button Click="spr" >Sprawdź</Button>
<TextBlock Text="{Binding Answers[0].IsSelected, Mode=TwoWay}" > </TextBlock>
It's in other controls part (above list box, but in FlipView)
Click method
private void spr(object sender, RoutedEventArgs e)
{
var ans = ((PrawoJazdyDataQuestion)this.flipView.SelectedItem).Answers;
foreach (var item in ans)
item.IsSelected = item.IsSelected ? false : true;
}
As I wrote, when i'm changing data from code side, it is changing content of element, but not appearance of ListBoxItem. And if I just select it on ListBox, it doesn't change the data in TextBlock neither in ListBox itself.
#edit2 fixing typos...
To change the IsSelected property of the ListBoxItem, you need to change the ListBox.ItemContainerStyle. See here:
<ListBox Grid.Row="1" Grid.ColumnSpan="3"
SelectionMode="Multiple" VerticalAlignment="Center"
ItemsSource="{Binding Answers}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding IsSelected}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Since the binding mode is TwoWay, selecting and deselecting the ListBoxItem's dynamically changes the content of the items to display either True or False.
Also notice how I changed the ListBox.ItemTemplate into a TextBlock instead of a ListBoxItem. The ItemTemplate defines the content of the ListBoxItem, so some type of content control is typically used. Below is the UI structure for the different layouts (this can be viewed using the WPF Tree Visualizer).
ListBoxItem as the ItemTemplate:
TextBlock as the ItemTemplate:
Edit
Also note that I removed the IValueConverter. Since your source and target properties are both bool, there is no need for a converter in this case. Try removing the converter references to see if that fixes the problem.
Do not bind to IsSelected on ListBoxItem. ListBox got SelectedItem or SelectedItems property, where You should pass item(s) to be selected, i.e. by binding.
Setting IsSelected property in model is not good idea. Better make SelectedItem notify property in ViewModel.

Different DataTemplate depending on the enumeration value of a property

I want something similar to what was asked here - but, I need the templates to depend on the value of a property, which is an enum.
The class looks simlar to this:
class ResultBlock
{
public string Name { get; set; }
public BlockType Type { get; set; }
public IList<ResultBlock> ChildBlocks { get; private set; }
}
Where BlockType has three different values, BLOCK, FILE, FOLDER - Now, I want to create a data template to present differently, depending on what value ResultBlock.Type has in the current object.
I tried doing this with DataType=, but that didn't work, obviously. I'm sure there is some way to do this very easily in XAML only.
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type docom:ResultBlock}" ItemsSource="{Binding ChildBlocks}">
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<DataTemplate DataType="{x:Type docom:BlockType.BLOCK}">
<TextBlock Text="BLOCK:{Binding Name}" />
</DataTemplate>
</StackPanel.Resources>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
You can trigger on the property, e.g.:
<HierarchicalDataTemplate DataType="{x:Type docom:ResultBlock}"
ItemsSource="{Binding ChildBlocks}">
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding BlockType}" Value="BLOCK">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!-- Data template for BLOCK blocks -->
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<!-- More triggers -->
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</HierarchicalDataTemplate>
Yes, it's verbose. (You can define the templates for the different types as keyed resources and then reference them in the Setters though)
<Window.Resources>
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
</Window.Resources>
<Grid>
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"/>
</Grid>
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate
SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is Task)
{
Task taskitem = item as Task;
if (taskitem.Priority == 1)
return
element.FindResource("importantTaskTemplate") as DataTemplate;
else
return
element.FindResource("myTaskTemplate") as DataTemplate;
}
return null;
}
}
This is implemented for ListBox but the idea can be same for DataGrid/TreeView .I hope this will help.
one way is to use a TemplateSelector and within the selector do what you want depending on your BlockType.
or you create wrapperviewmodels like:
public class ResultBlockBlock{}
public class ResultBlockFile{}
public class ResultBlockFolder{}
then you can go the DataTemplate DataType way

Categories