I need to bind a button to a Control Template. The XAML looks something like this:
Button Template="{Binding Status, Converter={StaticResource StatustoTemplate}}"
The converter (StatustoTemplate) runs fine as the status (which is an integer, but happy for it to be a string) changes:
public class StatustoTemplate : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value==1)
{
return ControlTemplateName1;
}
if (value==2)
{
return ControlTemplateName2;
}
}
}
Now, in what format can I send back ControlTemplate1, or ControlTemplate2? Let us assume that ControlTemplate1 and ControlTemplate2 are valid Control Templates as defined in the XAML. I now that it needs to return a ControlTemplate - but how to set it up?
my preferred approach is to use a Style with DataTriggers to switch Template, without converters
<Style TargetType="Button" x:Key="StatusButton"> <!--set BasedOn if there is a base Style-->
<Style.Triggers>
<DataTrigger Binding="{Binding Status}" Value="1">
<Setter Property="Template" Value="{StaticResource ControlTemplateName1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Status}" Value="2">
<Setter Property="Template" Value="{StaticResource ControlTemplateName2}"/>
</DataTrigger>
</Style.Triggers>
</Style>
and then apply this Style:
<Button Style="{StaticResource StatusButton}"/>
It is not easy for converter to find resource defined in XAML. I usually define one control template which has both definitions and switch them using Visibility. The code is just a short sample.
XAML
<Window>
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<ResourceDictionary>
<local:MyConverter x:Key="MyConverter"/>
<ControlTemplate x:Key="template">
<Grid>
<Grid Visibility="{Binding Status, Converter={StaticResource MyConverter}, ConverterParameter=1}">
<TextBox Text="1"/>
</Grid>
<Grid Visibility="{Binding Status, Converter={StaticResource MyConverter}, ConverterParameter=2}">
<TextBox Text="2"/>
</Grid>
</Grid>
</ControlTemplate>
</ResourceDictionary>
</Window.Resources>
<Grid>
<Button Template="{StaticResource template}"/>
</Grid>
</Window>
Converter
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((string)parameter == "1") ? Visibility.Visible : Visibility.Collapsed;
}
// ConvertBack
}
I have a MarkupExtension that might work for you. It is getting xaml defined Resources by ResourceKey.
First: abstract class StyleRefExtension:
public abstract class StyleRefExtension : MarkupExtension
{
protected static ResourceDictionary RD;
public string ResourceKey { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
return ProvideValue();
}
public object ProvideValue()
{
if (RD == null)
throw new Exception(
#"You should define RD before usage.
Please make it in static constructor of extending class!");
return RD[ResourceKey];
}
}
Second: class implementation as StyleRefExt
public class StyleRefExt : StyleRefExtension
{
static StyleRefExt()
{
RD = new ResourceDictionary
{
Source = new Uri("pack://application:,,,/##YOUR_ASSEMBLYNAME##;component/Styles/##YOUR_RESOURCE_DICTIONARY_FILE##.xaml")
};
}
}
Just replace ##YOUR_ASSEMBLYNAME## with the name of your assembly and ##YOUR_RESOURCE_DICTIONARY_FILE## with the filename of your ResourceDictionary (that is located in folder Styles).
Your Converter should look like this:
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value==1) {
StyleRefExt sr = new StyleRefExt {ResourceKey = "ControlTemplateName1"};
return sr.ProvideValue();
}
if (value==2) {
StyleRefExt sr = new StyleRefExt {ResourceKey = "ControlTemplateName2"};
return sr.ProvideValue();
}
}
Related
I try to create a matrix like view in WPF using a DataGrid control. I populate my items source list with a DataTable available thru a converter. Each row is an array of custom class type (MatrixCell).
In xaml, in my resource tab, I create a DataTemplate for my type, MatrixCell, but when running the application it is not applied. Does anyone have any idea what I'm doing wrong?
My DataGrid:
<DataGrid
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserSortColumns="False"
CanUserResizeColumns="False"
SelectionUnit="Cell"
IsReadOnly="True"
Name="matrixGrid">
<DataGrid.ItemsSource>
<Binding Path="Test" Converter="{StaticResource MatrixToDataViewConverter}"/>
</DataGrid.ItemsSource>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="LayoutTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform Angle="270"/>
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>
My converter:
public class MatrixToDataViewConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var myDataTable = new System.Data.DataTable();
if(value != null)
{
var test = value as ITest;
myDataTable.Columns.Add("-");
foreach (var column in test.Columns)
{
//populate columns
//...
}
var rows = new List<List<MatrixCell>>();
foreach (var matrixRow in test.MatrixRows)
{
var row = new List<MatrixCell>();
row.Add(new MatrixCell() { Content = $"{matrixRow.Id} {matrixRow.Name}" });
var temp = new MatrixCell[1 + test.Columns.Count];
foreach (var column in test.Columns)
{
//determine the Content value for each MatrixCell
//...
row.Add(new MatrixCell() { Content = content });
}
row.CopyTo(temp);
myDataTable.Rows.Add(temp);
}
}
return myDataTable.DefaultView;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
MatrixCell class:
public class MatrixCell
{
public string Content { get; set; }
public bool IsComparerDone { get; set; }
//public override string ToString()
//{
// return Content;
//}
}
DataTemplate:
<DataTemplate DataType="{x:Type viewModels:MatrixCell}">
<TextBlock Text="{Binding Content}">
<!--<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding IsComparerResult}" Value="True">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>-->
</TextBlock>
</DataTemplate>
Just for displaying the MatrixCell.Content I can override the ToString method and it works, but I would like to do it using a DataTemplate.
This is how my current application looks like:
matrix view
Welcome to SO! For future reference you'll get much better answers if you provide an MVCE, but I'll try to help...
You don't need that converter. Just bind to your list directly and declare a column of type DataGridTemplateColumn explicitly. Then inside that place a ContentControl and bind its Content property to its current DataContext (i.e. Content="{Binding}"). The ContentControl will then pick up your DataTemplate and populate itself accordingly.
I want to bind a TextBlock to an ObservableCollection<Log>. Each Log has different Type (enum), each Type should be resulting in different Run's Foreground (e.g. Error is Red, Success is Green).
I've done a quick read to these Q&A:
WPF and ObservableCollection<T>
In WPF how to define a Data template in case of enum?
WPF DataTemplate Binding depending on the type of a property
But my mind got stuck, because, I'm very new to WPF.
This is the class Log and enum Type:
public enum Type
{
Start = 1,
Stop = 0,
Info = 2,
Success = 4,
Error = 8
};
public class Log
{
public Type Type { get; set; }
public string Message { get; set; }
}
...and this is how I created the collection:
public partial class MainWindow : Window
{
ObservableCollection<Log> mLogCollection = new ObservableCollection<Log>();
public ObservableCollection<Log> LogCollection
{
get { return mLogCollection; }
}
public MainWindow()
{
DataContext = this;
mLogCollection.Add(new Log { Type = Log.Type.Error, Message = "Operation failed" });
mLogCollection.Add(new Log { Type = Log.Type.Success, Message = "Operation complete" });
}
How do I make everything like I want so it will be resulting in something like this?:
<TextBlock>
<Run Text="Operation failed" Foreground="Red"/>
<Run Text="Operation complete" Foreground="Green"/>
</TextBlock>
A balanced XAML and code behind solution is preferred rather than just full XAML.
I'm sorry if my explanation is not clear enough, I'm a bit sleepy right now.
Any help would be appreciated.
Here's the easiest way:
<StackPanel Orientation="Vertical">
<ItemsControl
ItemsSource="{Binding LogCollection}"
>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:Log">
<TextBlock
Text="{Binding Message}"
x:Name="MessageText"
/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Type}" Value="Error">
<Setter TargetName="MessageText" Property="Foreground" Value="Red" />
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="Success">
<Setter TargetName="MessageText" Property="Foreground" Value="Green" />
</DataTrigger>
<!--
Etc. for the other log type values.
-->
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But that's pure XAML and you want code behind. #Evk suggested a value converter for the foreground type, and that's a reasonable way to do it.
XAML:
<ItemsControl
ItemsSource="{Binding LogCollection}"
>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="local:Log">
<DataTemplate.Resources>
<local:LogTypeBrushConverter
x:Key="LogTypeBrush"
/>
</DataTemplate.Resources>
<TextBlock
Text="{Binding Message}"
Foreground="{Binding Type, Converter={StaticResource LogTypeBrush}}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
C#. You defined your enum as Log.Type which won't compile, because the Log class has a property named Type. So I renamed the enum to LogType.
public class LogTypeBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Color color = Colors.Black;
switch ((LogType)value)
{
case LogType.Start:
color = Colors.DodgerBlue;
break;
case LogType.Stop:
color = Colors.OrangeRed;
break;
case LogType.Info:
color = Colors.Blue;
break;
case LogType.Success:
color = Colors.Green;
break;
case LogType.Error:
color = Colors.Red;
break;
}
return new SolidColorBrush(color);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Incidentally, DataContext = this is a bad idea in general. On a Window it's relatively harmless, but try it on a UserControl and you start needing bizarre wokarounds. You can leave that out and bind to LogCollection like so:
ItemsSource="{Binding LogCollection, RelativeSource={RelativeSource AncestorType=Window}}"
I have a requirement to change a button's style based on a value in the data. It looks like a StyleSelector would work perfectly but there doesn't seem to be a way to set one for a button.
Is there a way to set a button style dynamically from data? Maybe even a pure XAML approach?
A more general way to accomplish the same thing:
SomeView.xaml
<UserControl>
<UserControl.Resources>
<converters:BooleanToStyleConverter x:Key="MyButtonStyleConverter"
TrueStyle="{StaticResource AmazingButtonStyle}"
FalseStyle="{StaticResource BoringButtonStyle}"/>
</UserControl.Resources>
<Grid>
<Button Style={Binding IsAmazingButton, Converter={StaticResource MyButtonStyleConverter}}/>
</Grid>
</UserControl>
BooleanToStyleConverter.cs
public class BooleanToStyleConverter : IValueConverter
{
public Style TrueStyle { get; set; }
public Style FalseStyle { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is bool && (bool) value)
{
return TrueStyle;
}
return FalseStyle;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This converter works in any view with any kind of control using whatever style you choose as long as you are binding to a Boolean property in your ViewModel to control the style switching. Easy to adapt it to other binding requirements though. This works in a DataTemplate as well.
You could place your Button Styles in a Resource Dictionary and bind the Style for the Button and use a Converter
ButtonStyles.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="ButtonStyle1" TargetType="Button">
<Setter Property="Background" Value="Green"/>
<Setter Property="FontSize" Value="12"/>
</Style>
<Style x:Key="ButtonStyle2" TargetType="Button">
<Setter Property="Background" Value="Red"/>
<Setter Property="FontSize" Value="14"/>
</Style>
</ResourceDictionary>
Then for the Button that has this requirement you bind Style to the property of interest
<Button ...
Style="{Binding Path=MyDataProperty,
Converter={StaticResource ButtonStyleConverter}}"/>
And in the Converter you load the ButtonStyles Resource Dictionary and return the desired Style based on the value
public class ButtonStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Uri resourceLocater = new Uri("/YourNameSpace;component/ButtonStyles.xaml", System.UriKind.Relative);
ResourceDictionary resourceDictionary = (ResourceDictionary)Application.LoadComponent(resourceLocater);
if (value.ToString() == "Some Value")
{
return resourceDictionary["ButtonStyle1"] as Style;
}
return resourceDictionary["ButtonStyle2"] as Style;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a field in my UI that can either take a date or a string.
I would like to switch on the type of control displayed depending on the data that is loaded in there or being input by the user.
In other words:
DatePicker if the user starts inputting some numbers (or data loaded is a date)
TextBox is a string input (or string loaded)
Can't find yet how to switch. Happy if you had some tip. Thank you!
You would need to use templates depending on a type. To do this you would need either have 2 properties , one with type of a property and another with the actial object (both notifying INotifyPropertyChanged ).
public object YourProperty
{
get
{
return yourProperty;
}
set
{
yourProperty = value;
OnPropertyChanged();
DateTime date;
if(yourProperty is String && DateTime.TryParse((string) yourProperty, out date))
{
YourProperty = date;
}
}
}
private object yourProperty = string.Empty;
//public Type YourPropertyType { get; set; }
You also can create a converter which will return the type of a property, so you can get rid of additional property (commented out above):
public class TypeOfConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value == null) ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
And finally bind a ContentControl to your property and select a template using the converter above:
<ContentControl Content="{Binding YourProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ContentControl.Resources>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=YourProperty,Converter={flowMathTest:TypeOfConverter}}" Value="{x:Type system:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Content, RelativeSource={RelativeSource AncestorType=ContentControl}, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=YourProperty,Converter={flowMathTest:TypeOfConverter}}" Value="{x:Type system:String}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Content, RelativeSource={RelativeSource AncestorType=ContentControl}, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Resources>
</ContentControl>
Thant should do it.
EDIT: I did not read the second part so the switching between controls should happen as user writes.
What you can do in this case is to change bindings to Mode=TwoWay and UpdateSourceTrigger=PropertyChanged and handle detection of type in code.
By default set YourProperty to string.Empty. Then on every change check if the text entered is already date using DateTime.Parse. If it is, set YourProperty to this date.
So I updated the code above to reflect these changes.
One of my views consists of 5 UserControls that each display data about a certain object. Let's say for example that the view displays the cows our company has, and on the screen cows 1 through 5 are displayed (each in their own UserControl).
What I want to do (but not sure is possible) is to bind the status of a cow to the style used in its respective UserControl. So we have a property status that could be ok, hungry, dead for example. In case the cow is ok I want to display a 'normal' style, if it's hungry I want the background to be red and if it's dead I want the text to be black and the fontsize increased.
I've added a simplified version of what I'm trying to achieve. My knowledge of WPF styles/resource dictionaries is still somewhat limited though.
What I basically want in code
A ViewModel with a Status property
class CowInfoViewModel : Screen
{
public string Name { get; set; }
public string Status { get; set; } //"ok", "hungry", "dead"
}
A View that retrieves a style or resourcedictionary
<UserControl x:Class="WpfModifyDifferentView.Views.CowInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- A reference to a ResourceDictionary with styles, that is bound to the 'Status' property -->
<StackPanel>
<TextBlock x:Name="Name" Text="Cow Name"/>
<TextBlock x:Name="Status" Text="Ok" />
</StackPanel>
</UserControl>
EDIT - Solution:
I did the following using Vale's answer:
In the xaml (reference to the converter):
<UserControl.Resources>
<Converters:CowStyleConverter x:Key="styleConverter" />
</UserControl.Resources>
In the xaml (elements):
<TextBlock x:Name="Name" Text="Cow Name" Style="{Binding Path=Style, ConverterParameter='TextBlockCowName', Converter={StaticResource styleConverter}}" />
The converter (note I left out the checks):
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var status = value.ToString();
var styleName = parameter.ToString();
_resourceDictionary.Source = new System.Uri(string.Format("pack://application:,,,/Resources/ScreenI2Style{0}.xaml", status));
return _resourceDictionary[styleName];
}
Then I created multiple ResourceDictionaries with styles such as:
<Style x:Key="TextBlockCowName" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource SomeBrush}" />
</Style>
You can bind UserControl Style property to Status and use a converter.
<UserControl x:Class="WpfModifyDifferentView.Views.CowInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfModifyDifferentView"
Style="{Binding Path=Status, Converter={StaticResource myConverter}}">
<UserControl.Resources>
<local:MyConverter x:Key="myConverter" />
</UserControl.Resources>
I assume that your converter is in WpfModifyDifferentView directly.
Converter will look like this:
public class MyConverter : IValueConverter {
private ResourceDictionary dictionary;
public MyConverter() {
if (dictionary == null) {
dictionary = new ResourceDictionary();
dictionary.Source = new Uri("pack://application:,,,/WpfModifyDifferentView;Component/Resources/Styles.xaml");
}
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
switch (value.ToString()) {
case "ok":
return dictionary["myKeyForOkStyle"] as Style;
case "hungry":
return dictionary["myKeyForHungryStyle"] as Style;
case "dead":
return dictionary["myKeyForDeadStyle"] as Style;
default:
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
You need to specify the correct URI of course.