I have a progress bar that I want to change color depending on a boolean value; true is green and false is red. I have code that seems like it should work (it returns the correct value when I bind it to a textbox) but not when it's the color property of the progress bar. The converter is defined as this (in App.xaml.cs since I want to access it anywhere):
public class ProgressBarConverter : System.Windows.Data.IValueConverter
{
public object Convert(
object o,
Type type,
object parameter,
System.Globalization.CultureInfo culture)
{
if (o == null)
return null;
else
//return (bool)o ? new SolidColorBrush(Colors.Red) :
// new SolidColorBrush(Colors.Green);
return (bool)o ? Colors.Red : Colors.Green;
}
public object ConvertBack(
object o,
Type type,
object parameter,
System.Globalization.CultureInfo culture)
{
return null;
}
}
I then add the following to the App.xaml (so it can be a global resource):
<Application.Resources>
<local:ProgressBarConverter x:Key="progressBarConverter" />
<DataTemplate x:Key="ItemTemplate">
<StackPanel>
<TextBlock Text="{Binding name}" Width="280" />
<TextBlock Text="{Binding isNeeded,
Converter={StaticResource progressBarConverter}}" />
<ProgressBar>
<ProgressBar.Foreground>
<SolidColorBrush Color="{Binding isNeeded,
Converter={StaticResource progressBarConverter}}" />
</ProgressBar.Foreground>
<ProgressBar.Background>
<SolidColorBrush Color="{StaticResource PhoneBorderColor}"/>
</ProgressBar.Background>
</ProgressBar>
</StackPanel>
</DataTemplate>
</Application.Resources>
I added the following to MainPage.xaml to display them:
<Grid x:Name="LayoutRoot" Background="Transparent">
<ListBox x:Name="listBox"
ItemTemplate="{StaticResource ItemTemplate}"/>
</Grid>
And then in MainPage.xaml.cs, I define a class to hold the data and bind it to the listBox:
namespace PhoneApp1
{
public class TestClass
{
public bool isNeeded { get; set; }
public string name { get; set; }
}
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
var list = new LinkedList<TestClass>();
list.AddFirst(
new TestClass {
isNeeded = true, name = "should be green" });
list.AddFirst(
new TestClass {
isNeeded = false, name = "should be red" });
listBox.ItemsSource = list;
}
}
}
I've attached a minimal working example so it can just be built and tested. An image of the output is here:
It returns the values from the converter for the textbox but not the progress bar. When I run the debugger, it doesn't even call it.
Thanks for any help!
Try to modify your converter to return a SolidColorBrush and then bind directly to your ProgressBars Foreground property.
Related
Good afternoon everyone. To demonstrate what I'm after, let's say that I have the following classes:
public enum Field {FirstName, LastName, Address, City, State, Zipcode};
public class Item
{
public Field Id {get; set;}
public string Name {get; set;}
public void Item(Field field, string name)
{
Id = field;
Name = name;
}
}
public class Items
{
private List<Item> _Items;
public void AddItem(Field field, string name)
{
_Items.Add(new Item(field, name));
}
public Item GetItem(Field field)
{
foreach(Item item in _Items)
{
if( item.Id == field ) return item;
}
return null;
}
}
public Window SomeForm : Window
{
private Items _Items;
public SomeForm()
{
_Items = new Items();
_Items.Add(Field.FirstName, "First Name");
_Items.Add(Field.Address, "Address");
DataContext = Items;
InitializeComponent();
}
}
And then in the XAML:
<StackPanel Orientation="Horizontal">
<Label DataContext="{Binding GetItem(Field.FirstName)}" Content="{Binding Name}" />
<Label DataContext="{Binding GetItem(Field.Address)}" Content="{Binding Name}" />
</StackPanel>
Ideally, I would like to do something where ControlField is an attached property:
<StackPanel Orientation="Horizontal">
<Label Style="{StaticResource MyLabel}" ControlField="{x:Static Field.FirstName}" />
<Label Style="{StaticResource MyLabel}" ControlField="{x:Static Field.LastName}" />
</StackPanel>
and then the binding of DataContext and label Content would occur in the MyLabel style.
I know GetItem(Field field) won't work (I think) but the following won't work (for several reasons) "{Binding DataContext[Field.FirstName]}".
I have worked with various things to no avail. I have kept my description somewhat high level so describe what I'm trying to accomplish. With this in mind, how I can go about this please? Thank you in advance.
You can try it this way.
NuGet package
CommunityToolkit.Mvvm
MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using System.Collections.ObjectModel;
namespace WpfApp2;
public partial class MainWindowViewModel : ObservableObject
{
public MainWindowViewModel()
{
Items.Add(new Item(Field.FirstName, "First Name"));
Items.Add(new Item(Field.LastName, "Last Name"));
}
[ObservableProperty]
private ObservableCollection<Item> items = new ObservableCollection<Item>();
}
FieldToValueConverter.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace WpfApp2;
public class FieldToValueConverter : IValueConverter
{
public object? Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is IEnumerable<Item> items &&
parameter is string fieldName &&
Enum.TryParse<Field>(fieldName, out var field) is true)
{
return items.FirstOrDefault(x => x.Id == field)?.Name;
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public MainWindowViewModel ViewModel { get; } = new();
}
MainWindow.xaml
<Window
x:Class="WpfApp2.MainWindow"
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:local="clr-namespace:WpfApp2"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
x:Name="ThisWindow"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.Resources>
<local:FieldToValueConverter x:Key="FieldToValueConverter" />
</Window.Resources>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding ElementName=ThisWindow, Path=ViewModel.Items, Converter={StaticResource FieldToValueConverter},ConverterParameter='FirstName'}"/>
<Label Content="{Binding ElementName=ThisWindow, Path=ViewModel.Items, Converter={StaticResource FieldToValueConverter},ConverterParameter='LastName'}"/>
</StackPanel>
</Window>
There is no way to do what I would like to do. I cannot pass in an enum (for instance) for the array subscript. So, this is what I have done.
I added an attached property Field which takes an enum.
I made a class (WindowBase) that inherits from Window. My windows then inherit from WindowBase.
In WindowBase, in the constructor, I subscribe to each control's Load event and within that event I attach my own class to the control's datacontext which defines the label, text and other things.
Each control style contains the binding information to the class's property (Label.Contents binds to Label, TextBox.Text binds to Text, etc.)
Then, when laying out a form, each control in XAML then only needs to only define the control type, the style and the attached property enum. The styles handle the appropriate binding and WindowBase handles attaching various classes to each control.
I have written sample application which has 6 items in Field (FirstName, LastName, etc.), on the form I have 6 Labels and 6 TextBox's. This paradigm works very well for me.
So, this isn't an answer on how to use an enum for an array subscript in XAML but the workaround works great for me.
I have a Forecast class. One of the field's type is Enum:
enum GeneralForecast
{
Sunny,
Rainy,
Snowy,
Cloudy,
Dry
}
class Forecast
{
public GeneralForecast GeneralForecast { get; set; }
public double TemperatureHigh { get; set; }
public double TemperatureLow { get; set; }
public double Percipitation { get; set; }
}
I display list of forecasts on the ListBox and I want to set the BackgroundColor of the item in the ListBox depend on GeneralForecast.
So I have created Converter:
class GeneralForecastToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var gf = (GeneralForecast) value;
switch (gf)
{
case GeneralForecast.Cloudy:
return "FF1D1D1D";
case GeneralForecast.Dry:
return "55112233";
case GeneralForecast.Rainy:
return "88FF5522";
case GeneralForecast.Snowy:
return "9955FF22";
case GeneralForecast.Sunny:
return "FF11FF99";
}
return "FFFFFFFF";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Here is my XAML:
<Page.Resources>
<local:GeneralForecastToBrushConverter x:Key="gf2color"/>
</Page.Resources>
<ListBox ItemsSource="{Binding}" Grid.Row="2" HorizontalAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="4" BorderBrush="Black" Padding="4"
BorderThickness="2"
Background="{Binding GeneralForecast, Converter={StaticResource gf2color}}">
<StackPanel Orientation="Horizontal">
<TextBlock FontSize="20" FontWeight="Bold"
Text="{Binding GeneralForecast}"/>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If I debug my Converter I can see that it returns different colors, but I have all items the same color. Why ?
When you write something like this in XAML:
<Button Background="#FF11111" />
The Xaml parser will convert that string to its equivalent color at runtime.
But when you assign color in C# somehow, you may not set the color as string.
Instead you should use something like SolidColorBrush instances.
So return some variable which is Brush, such as solid or gradient color brushes.
Let me know if any other information is needed.
This may get a tad long, but here goes. I have created a small, wizard-style sample app using the MVVM pattern (basically a dumbed-down version of the code in my "real" app). In this app, the main window moves from through a List<..> of view models, with each view model displaying its associated view. I have 2 view model classes that are essentially identical, and they display the same view.
On the view is a combo box, populated with an array of float. The SelectedItem is bound to a float property on the view model. I have created a template for the combo box to display each item as a TextBlock, with the text taking the float value and going through a value converter.
The problem, when I switch back and forth between view models, all works fine as long as every view model I switch to is of the same class. As soon as I change the current page to an instance of a different view model, the value converter's Convert gets called with a 'value' parameter that is a zero-length string. Up til then, Convert was only being called with a float, as I would expect.
My question : why is the converter being called with the empty string ONLY in the case of switching view model classes?
I am attaching the main window XAML and view model, as well as the view/view models displayed for each "page". You'll notice that the main window view model has a list containing 2 instances of PageViewModel and 2 instances of OtherViewModel. I can switch back and forth between the first 2 fine, and the value converter only gets called with a float value. Once I switch to the first OtherViewModel instance, the converter gets an "extra" call with an empty string as the value.
Code snippets :
MainWindow
<Grid.Resources>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<local:PageView />
</DataTemplate>
<DataTemplate DataType="{x:Type local:OtherViewModel}">
<local:PageView />
</DataTemplate>
</Grid.Resources>
<!-- Page -->
<ContentControl Margin="5,5,5,35"
Height="100"
IsTabStop="False"
Content="{Binding CurrentPage}" />
<!-- Commands -->
<Button Margin="5,115,0,0"
Width="75"
Content="< Back"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Command="{Binding BackCommand}" />
<Button Margin="85,115,0,0"
Width="75"
Content="Next >"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Command="{Binding NextCommand}" />
MainWindowViewModel
public MainWindowViewModel()
{
m_pages = new List<BaseViewModel>();
m_pages.Add(new PageViewModel(1, 7f));
m_pages.Add(new PageViewModel(2, 8.5f));
m_pages.Add(new OtherViewModel(3, 10f));
m_pages.Add(new OtherViewModel(4, 11.5f));
m_currentPage = m_pages.First();
m_nextCommand = new BaseCommand(param => this.OnNext(), param => this.EnableNext());
m_backCommand = new BaseCommand(param => this.OnBack(), param => this.EnableBack());
}
// Title
public string Title
{
get
{
return (CurrentPage != null) ? CurrentPage.Name : Name;
}
}
// Pages
BaseViewModel m_currentPage = null;
List<BaseViewModel> m_pages = null;
public BaseViewModel CurrentPage
{
get
{
return m_currentPage;
}
set
{
if (value == m_currentPage)
return;
m_currentPage = value;
OnPropertyChanged("Title");
OnPropertyChanged("CurrentPage");
}
}
// Back
ICommand m_backCommand = null;
public ICommand BackCommand
{
get
{
return m_backCommand;
}
}
public void OnBack()
{
CurrentPage = m_pages[m_pages.IndexOf(CurrentPage) - 1];
}
public bool EnableBack()
{
return CurrentPage != m_pages.First();
}
// Next
ICommand m_nextCommand = null;
public ICommand NextCommand
{
get
{
return m_nextCommand;
}
}
public void OnNext()
{
CurrentPage = m_pages[m_pages.IndexOf(CurrentPage) + 1];
}
public bool EnableNext()
{
return CurrentPage != m_pages.Last();
}
}
Notice the 2 instance of one view model followed by 2 instances of the other.
PageView
<Grid.Resources>
<x:Array x:Key="DepthList"
Type="sys:Single">
<sys:Single>7</sys:Single>
<sys:Single>8.5</sys:Single>
<sys:Single>10</sys:Single>
<sys:Single>11.5</sys:Single>
</x:Array>
<local:MyConverter x:Key="MyConverter" />
</Grid.Resources>
<TextBlock Text="Values:"
Margin="5,5,0,0">
</TextBlock>
<ComboBox Width="100"
Height="23"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="5,25,0,0"
DataContext="{Binding}"
SelectedItem="{Binding Depth}"
ItemsSource="{Binding Source={StaticResource DepthList}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MyConverter}}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
PageViewModel/OtherViewModel/MyConverter
public class PageViewModel : BaseViewModel
{
public PageViewModel(int index, float depth)
{
Depth = depth;
Name = "Page #" + index.ToString();
}
public float Depth
{
get;
set;
}
}
public class OtherViewModel : BaseViewModel
{
public OtherViewModel(int index, float depth)
{
Depth = depth;
Name = "Other #" + index.ToString();
}
public float Depth
{
get;
set;
}
}
[ValueConversion(typeof(DateTime), typeof(String))]
public class MyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Debug.WriteLine("IValueConverter.Convert : received a " + value.GetType().Name);
string text = "";
if (value is float)
{
text = value.ToString();
}
else
{
throw new ArgumentException("MyConverter : input value is NOT a float.");
}
return text;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return float.Parse(value as string);
}
}
Note: I can remove the exception in the Convert method, and everything seems to work fine. But, I would like to know why this is happening. Why is the converter getting an empty string instead of the expected float, and only when we switch view models?
Any insights would be greatly appreciated. Thanks in advance...
Joe
I've had the same issue (with an enum instead of a float).
When the View is closed the ComboBox Selection is emptied. You can check this by handling SelectionChanged event and inspecting the SelectionChangedEventArgs RemovedItems collection.
This ends in String.Empty being passed into your ValueConverter.
In my case, I have modified the ValueConverter.Convert to allow string.Empty as a valid value, and return string.Empty.
This is the code I used:
// When view is unloaded, ComboBox Selection is emptied and Convert is passed string.Empty
// Hence we need to handle this conversion
if (value is string && string.IsNullOrEmpty((string)value))
{
return string.Empty;
}
Try
public BaseViewModel
{
public virtual float Depth{get;set;}
...
}
Then
public class PageViewModel : BaseViewModel
{
...
public override float Depth { get; set; }
}
and
public class OtherViewModel : BaseViewModel
{
...
public override float Depth { get; set; }
}
Then you only need one DataTemplate
<Grid.Resources>
<DataTemplate DataType="{x:Type local:BaseViewModel}">
<local:PageView />
</DataTemplate>
</Grid.Resources>
I'm guessing the strange value being passed to the converter is due to DataTemplates being switched.
Not tested
Using WPF, I want to bind the header of a GroupBox to the typename of a polymorphic class. So if I have a class called Element, and two classes that derive from Element, such as BasicElement and AdvancedElement, I want the header of the GroupBox to say "BasicElement" or "AdvancedElement". Here is the xaml I am using for the GroupBox. It's part of a DataTemplate being used by an ItemsControl. I'm hoping for something in place of Path=DerivedTypeNameOf(group) in the XAML, where group is each group in the groups array.
Note that the ObjectInstance of TheData is being set to a valid instance of GroupSet which holds an array of some BasicGroups and AdvancedGroups.
Here are the pertinent code-behind classes:
public class Group
{
public string groupName;
public string df_groupName
{
get { return this.groupName; }
set { this.groupName = value; }
}
}
public class BasicGroup : Group
{
}
public class AdvancedGroup : Group
{
}
public class GroupSet
{
public Group [] groups;
public Group [] df_groups
{
get { return this.groups; }
set { this.groups = value; }
}
};
Here's the XAML:
<UserControl.Resources>
<ResourceDictionary>
<ObjectDataProvider x:Key="TheData" />
<DataTemplate x:Key="GroupTemplate">
<GroupBox Header="{Binding Path=DerivedTypeNameOf(group)}">
<TextBox Text="This is some text"/>
</GroupBox>
</DataTemplate>
</ResourceDictionary>
</UserControl.Resources>
<ItemsControl ItemsSource="{Binding Source={StaticResource TheData}, Path=groups}" ItemTemplate="{StaticResource GroupTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
You could always use a ValueConverter to get the type:
public class TypeNameConverter : IValueConverter {
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture) {
return value.GetType().Name;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture) {
throw NotImplementedException();
}
}
That would allow you to have any type in your collection without any need for it to implement a property to get the value. Otherwise, just do as David says and implement a property to give the result. You wouldn't even need to implement it in every class if there is general inheritance from a base class. Just implement it in the base with GetType().Name and you'll always get the correct value.
Why not just add
public abstract string DerivedTypeName { get; set; }
to your base class and override it for each derived type then you are simply binding to a string.
In Silverlight, MVVM I have to create a property window, but I have just a
List<AProperty> object, where AProperty is an abstract class with some child classes.
I want to bind it to a Silverlight control (but to which one?), with some conditions:
some child-properties must be shown as a textbox, some as a checkbox, and some as a combobox. It comes from the dynamic type.
the AProperty class has a PropertyGroup and a Name field. The order must be alphabetic (PropertyGroup > Name)
Any idea or working example?
My code:
public abstract class AProperty {
private String _Name;
private String _Caption;
private String _PropertyGroup;
public String Name {
get { return _Name; }
set { _Name = value; }
}
public String Caption {
get { return _Caption; }
set { _Caption = value; }
}
public String PropertyGroup {
get { return _PropertyGroup; }
set { _PropertyGroup = value; }
}
}
List<AProperty> Properties;
And the xaml:
<ListBox ItemsSource="{Binding ... Properties ...}">
<!-- Here order the items -->
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Width="250"
Text="{Binding Path=Caption}" />
<!-- And here need I a textbox, or a checkbox, or a combobox anyway -->
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I've found value converters just for a control attribute, not for a whole stackpanel content.
You could use a value converter to check what Type the object is and return the correct control type.
Do you have any sample code so I could give a better example?
The ValueConverter Andy mentions should work. Underneath your Textblock have a ContentPresenter, and bind it's Content.
Content={Binding , ConverterParameter={StaticResource NAMEOFCONVERTER}}.
The binding to the property is empty since you already have the object you want as the context. Then just check the type and return the control you want. you may want to make usercontrol's that are just Textblocks, etc. so that you can set the binding there, otherwise you will have to do it in code.
Thanks Jesse and Andy, here is the solution
The converter:
public class PropertyConverter : IValueConverter {
public object Convert( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
Binding ValueBinding = new Binding() {
Source = value,
Path = new PropertyPath( "Value" ),
Mode = BindingMode.TwoWay
};
Binding EditableBinding = new Binding() {
Source = value,
Path = new PropertyPath( "Editable" ),
Mode = BindingMode.TwoWay
};
if( value is PropertyString ) {
TextBox tb = new TextBox();
tb.SetBinding( TextBox.TextProperty, ValueBinding );
tb.SetBinding( TextBox.IsEnabledProperty, EditableBinding );
return tb;
} else if( value is PropertyBoolean ) {
CheckBox cb = new CheckBox();
cb.SetBinding( CheckBox.IsCheckedProperty, ValueBinding );
cb.SetBinding( CheckBox.IsEnabledProperty, EditableBinding );
return cb;
} ....
return null;
}
public object ConvertBack( object value, Type targetType, object parameter, System.Globalization.CultureInfo culture ) {
throw new NotImplementedException();
}
}
And the xaml code:
...
xmlns:Converters="clr-namespace:MyConverters"
...
<UserControl.Resources>
<Converters:PropertyConverter x:Key="PropertyInput"/>
</UserControl.Resources>
...
<ListBox ItemsSource="{Binding Path=ItemProperties.GeneralProperties}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="180" />
<ColumnDefinition Width="320" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Grid.Column="0" />
<ContentPresenter Content="{Binding Converter={StaticResource PropertyInput}}" Grid.Column="1" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Two artikel:
http://shawnoster.com/blog/post/Dynamic-Icons-in-the-Silverlight-TreeView.aspx
http://www.silverlightshow.net/items/Silverlight-2-Getting-to-the-ListBoxItems-in-a-ListBox.aspx