I've got a Observable collection of custom objects and a public dictionary variable.
I would like the "BrandName" attribute to act as the Key for the "Brands" dictionary and bind the colour to the button. How would I go about doing this? The dictionary variable is outside of the class.
C# Code:
private ObservableCollection<BusService> BusServicesGUI;
public Dictionary<String, Brush> Brands;
public MainWindow(Dictionary<String, BusService> busServices)
{
InitializeComponent();
BusServicesGUI = new ObservableCollection<BusService>(BusServices.Values);
lstMachineFunctions.ItemsSource = BusServicesGUI;
lstMachineFunctions.Items.Refresh();
}
C# Class:
public class BusService
{
public string ServiceNumber { get; set; }
public string BrandName { get; set; }
public List<Location> Locations { get; set; }
public BusService(string brandName, string serviceNumber)
{
BrandName = brandName;
ServiceNumber = serviceNumber;
Locations = new List<Location>();
}
}
XAML CODE:
<StackPanel x:Name="ServiceStack">
<ItemsControl x:Name="lstMachineFunctions">
<ItemsControl.ItemTemplate >
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<usercontrols:BusServiceCard/>
<Button Tag="{Binding ServiceNumber}" Background="{Binding Brands[BrandName]}" Height="50" Click="ButtonCl"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
As you can see from the XAML my current attempts have been trying Background="{Binding Brands[BrandName]}" This has however not worked, any help would be much appreciated.
You can use an IValueConverter to pefrom this operation.
public class BrandColorConverter : IValueConverter
{
public Dictionary<String, Brush> Brands = new Dictionary<string, Brush>()
{
{ "brand1", Brushes.Red },
{ "brand2", Brushes.Blue }
};
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
if (!(value is BusService))
return Binding.DoNothing;
var busService = (BusService)value;
if (!Brands.ContainsKey(busService.BrandName))
return Binding.DoNothing;
return Brands[busService.BrandName];
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
In xaml, add it as a static resuorce:
<Window.Resources>
<local:BrandColorConverter x:Key="BrandColorConverter"/>
</Window.Resources>
And use it in your button:
<Button Tag="{Binding ServiceNumber}"
Background="{Binding Converter={StaticResource BrandColorConverter}}"
Height="50"
Click="ButtonCl"/>
This binding goes to the current element, so the whole BusService object will be passed to the converter.
Hope it solves your problem.
I would strongly advise you to look into MVVM pattern if you are going to use WPF with data binding, as it makes things much more streamlined.
Related
I am making a Bing Map program using C# WPF MVVM.
Whenever the map view in Bing Map changes, I want to bind a BoundingRectangle to the ViewModel and save it.
But BoundingRectangle is just a setter.
So, I am trying to connect an event rather than a general Command binding and send it as a CommandParameter.
I wrote below
Xaml
<Grid Grid.Row="0" Grid.Column="1">
<bm:Map x:Name="bm"
Margin="0,10,10,10"
CredentialsProvider="..."
Mode="AerialWithLabels"
BorderBrush="{StaticResource DefalutBorderBrush}"
BorderThickness="1"
Center="{Binding CenterLocation, Mode=TwoWay,Converter={StaticResource Converter}}"
ZoomLevel="{Binding CurrentZoomLevel, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="ViewChangeOnFrame">
<i:InvokeCommandAction Command="{Binding ViewChangedCommand, Mode=OneWay, Converter={StaticResource TestConverter}}"
CommandParameter="{Binding BoundingRectangle, Mode=OneWay, ElementName=bm, Converter={StaticResource ConverterRect}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</bm:Map>
View Model
public class TestViewModel
{
private double[] _boundingBox;
public double[] BingMapBoundingBox
{
get
{
return _boundingBox;
}
set
{
SetProperty(ref _boundingBox, value);
}
}
public ICommand ViewChangedCommand
{
get;
set;
}
public TestViewModel()
{
ViewChangedCommand = new RelayCommand<double[]>(ViewChanged);
}
public void ViewChanged(double[] boundingBox)
{
BingMapBoundingBox = boundingBox;
}
}
Converter
public class LocationRectToDoubleArrayConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double[] resultArry = new double[4] { 0, 0, 0, 0 };
if (value == null)
return resultArry;
LocationRect currLocation = value as LocationRect;
resultArry[0] = currLocation.Northwest.Longitude;
resultArry[1] = currLocation.Northwest.Latitude;
resultArry[2] = currLocation.Southeast.Longitude;
resultArry[3] = currLocation.Southeast.Latitude;
return resultArry;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
LocationRect resultLocation = new LocationRect();
if (value is null)
return resultLocation;
double[] dblArray = value as double[];
resultLocation.Northwest.Longitude = dblArray[0];
resultLocation.Northwest.Latitude = dblArray[1];
resultLocation.Southeast.Longitude = dblArray[2];
resultLocation.Southeast.Latitude = dblArray[3];
return resultLocation;
}
}
The event is executed every time the View is changed, but CommandParameter is executed only once when the program is first executed, and is not executed after that.
Whenever the View changes, I want the BoundingRectangle to come in as a CommandParameter. What should I do?
Sorry for the lack of explanation as English is not my native language.
Thank You
C# Code-
namespace WPFDataBinding
{
public partial class MainWindow : Window
{
public Person Obj { get; set; }
public MainWindow()
{
InitializeComponent();
Obj = new Person();
List<string> subjects1 = new List<string>();
subjects1.Add("C"); subjects1.Add("C++"); subjects1.Add("C#");
List<string> subjects2 = new List<string>();
subjects2.Add("JAVA"); subjects2.Add("JS"); subjects2.Add("CSS");
Obj.studDetail.Add("Kush", subjects1);
Obj.studDetail.Add("Yash", subjects2);
DataContext = this;
}
public class Person
{
private Dictionary<string, List<string>> StudDetail = new Dictionary<string, List<string>>();
public Dictionary<string, List<string>> studDetail
{
get { return StudDetail; }
set { StudDetail = value; }
}
}
}
}
WPF code------
<ListBox FontSize="20" Height="Auto" Width="Auto" MinHeight="100" MinWidth="100" Background="Gray">
<Label Content="{Binding Obj}">
<Label.ContentTemplate>
<DataTemplate>
<ListBox Height="Auto" FontSize="20" MinHeight="100"
MinWidth="100" Width="Auto" Name="Sub1"
ItemsSource="{Binding studDetail}"/>
</DataTemplate>
</Label.ContentTemplate>
</Label>
</ListBox>
I wrote this code but the result is not what i expected.Do anyone know what to do in this situation? If yes, Plz Help to solve it.
You can use an IValueConverter to change the data in to a usable form.
If you just want all of the strings in the list of strings clumped together in one list, you can use this converter:
public class StudentDetailsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var data = value as Dictionary<string, List<string>>;
return data.Values.SelectMany(v => v);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
You will have to add this as a resource someplace accessible in your XAML; the Window is one possibility.
<Window.Resources>
<local:StudentDetailsConverter x:Key="StudentDetailsConverter" />
</Window.Resources>
Then change your binding to this:
ItemsSource="{Binding Path=studDetail, Converter={staticResource StudentDetailsConverter}}"
If you want something different, please update your question.
I have an object based on byte data with over 200 properties that I care about, in the sense that I want to (1) know the value, and (2) know when the value changed from one message to the next.
A snippet of the XAML I am using:
<Label Content="Prop Name" />
<TextBlock Text="{Binding PropName}"
Background="{Binding PropName,
Converter={StaticResource CompareToLastValueConverter}}" />
Currently, I have these lines pasted for EACH property, with appropriate grid location settings.
My question is this: is there a nice way to create a nested WPF UserControl that takes a generic object property from the model and handles assigning the name (with spaces) to the Label, then assigning the value of the property to the TextBlock like the example above?
Also, is this the best way to think about this problem, or am I missing a link in the "WPF way" of doing things?
I've often wanted to try this. I'd create an ItemsControl template for PropertyInfo.
I created a test class:
public class MyClass
{
public string PropertyTest1 {get;set;}
public string PropertyTest2 { get; set; }
public string PropertyTest3 { get; set; }
public string PropertyTest4 { get; set; }
}
To display the properties of. In my data context for the display, I've got two things to bind to. A list of PropertyInfos, and the object in question. Since the PropertyInfo is static, you might be able to do this a better way using a converter or something, and not need to bind it to a property:
public PropertyInfo[] Properties
{
get { return typeof(MyClass).GetProperties(); }
}
public MyClass MyObject
{
get { return new MyClass { PropertyTest1 = "test", PropertyTest3 = "Some string", PropertyTest4 = "Last Property" }; }
}
Now, displaying the properties is easy:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}" Grid.IsSharedSizeScope="True">
<ItemsControl.Resources>
<local:PropertyInfoValueConverter x:Key="PropertyInfoValueConverter"/>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" Margin="4,2"/>
<TextBlock Grid.Column="1" Margin="4,2"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But these are 'static', we can't bind to any values. A way to get around that is to use the Tag property, and a multi-binding converter:
So lets add Tag="{Binding MyObject}" to our ItemsSource, and throw that and the PropertyInfo into a value converter for our second textblock:
<TextBlock Grid.Column="1" Margin="4,2">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource PropertyInfoValueConverter}">
<Binding Path=""/>
<Binding ElementName="PropertyDisplay" Path="Tag"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
The converter is actually pretty simple, especially since you're not using text-boxes (so only going the read-only direction):
public class PropertyInfoValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
PropertyInfo propertyInfo = values[0] as PropertyInfo;
return propertyInfo.GetValue(values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This is the result:
You say you want spaces for the names, that could be done with a converter with some logic looking for whatever naming convention you've got (spaces before capital letters?).
It would be fun to play with template selectors to choose boolean, string, float templates and treat them differently. (Checkboxes, text, 00.00 formatted text etc)
Edit: Exploring Template Selector
Here's a sample template selector:
public class PropertyInfoTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate IntegerTemplate { get; set; }
public DataTemplate DecimalTemplate { get; set; }
public DataTemplate BooleanTemplate { get; set; }
public DataTemplate DefaultTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
PropertyInfo propertyInfo = item as PropertyInfo;
if (propertyInfo.PropertyType == typeof(string))
{
return StringTemplate;
}
else if (propertyInfo.PropertyType == typeof(int))
{
return IntegerTemplate;
}
else if (propertyInfo.PropertyType == typeof(float) || propertyInfo.PropertyType == typeof(double))
{
return DecimalTemplate;
}
else if (propertyInfo.PropertyType == typeof(bool))
{
return BooleanTemplate;
}
return DefaultTemplate;
}
}
Our ItemsControl is now simply:
<ItemsControl x:Name="PropertyDisplay" ItemsSource="{Binding Properties}"
Grid.IsSharedSizeScope="True"
Tag="{Binding MyObject}"
ItemTemplateSelector="{StaticResource PropertyInfoTemplateSelector}"
Margin="20"/>
I also added spaces in names using this converter:
public class PropertyInfoNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string text = value as string;
if (string.IsNullOrWhiteSpace(text))
return string.Empty;
StringBuilder newText = new StringBuilder(text.Length * 2);
newText.Append(text[0]);
for (int i = 1; i < text.Length; i++)
{
if (char.IsUpper(text[i]))
if ((text[i - 1] != ' ' && !char.IsUpper(text[i - 1])) ||
(char.IsUpper(text[i - 1]) &&
i < text.Length - 1 && !char.IsUpper(text[i + 1])))
newText.Append(' ');
newText.Append(text[i]);
}
return newText.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
(Credit to this: https://stackoverflow.com/a/272929/1305699).
Updating our class to contain some boolean and fload fields:
class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
return loai.ToDisplaytring();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
<ListView ItemsSource="{Binding ListEnum" Margin="0,51,0,0">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=.,Converter={StaticResource EnumToStringConverter}}" HorizontalAlignment="Center" FontSize="18"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I am a new guy to XAML.
I just want to display enum in listview.
But it has some problem of binding itself with binding:
{Binding Path=.,Converter={StaticResource EnumToStringConverter}}
This is simpler:
<ListView x:Name="ListViewInstance" ItemsSource="{Binding ListEnum}" Margin="0,51,0,0">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" HorizontalAlignment="Center" FontSize="18"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
That's the binding that gets the items, it automatically makes the item.ToString()
and to show all the values in the DataContext, for instance:
public List<Devices> ListEnum { get { return typeof(Devices).GetEnumValues().Cast<Devices>().ToList(); } }
In case you need a converter do the following:
public class EnumToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return what you need to convert from value
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and then in XAML
<Window.Resources>
<local:EnumToStringConverter x:Key="EnumToStringConverter"/>
</Window.Resources>
<ListView x:Name="ListViewInstance" ItemsSource="{Binding ListEnum}" Margin="0,51,0,0">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding, Converter={StaticResource EnumToStringConverter}}" HorizontalAlignment="Center" FontSize="18"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
You can use the enum with attributes and the create a list for listbox using extension methods.You can refer the below code.
<ListBox Width="200" Height="25" ItemsSource="{Binding ComboSource}"
DisplayMemberPath="Value"
SelectedValuePath="Key"/>
public class MainViewModel
{
public List<KeyValuePair<RentStatus, string>> ComboSource { get; set; }
public MainViewModel()
{
ComboSource = new List<KeyValuePair<RentStatus, string>>();
RentStatus re=RentStatus.Active;
ComboSource = re.GetValuesForComboBox<RentStatus>();
}
}
public enum RentStatus
{
[Description("Preparation description")]
Preparation,
[Description("Active description")]
Active,
[Description("Rented to people")]
Rented
}
public static class ExtensionMethods
{
public static List<KeyValuePair<T, string>> GetValuesForComboBox<T>(this Enum theEnum)
{
List<KeyValuePair<T, string>> _comboBoxItemSource = null;
if (_comboBoxItemSource == null)
{
_comboBoxItemSource = new List<KeyValuePair<T, string>>();
foreach (T level in Enum.GetValues(typeof(T)))
{
string Description = string.Empty;
FieldInfo fieldInfo = level.GetType().GetField(level.ToString());
DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes != null && attributes.Length > 0)
{
Description = attributes.FirstOrDefault().Description;
}
KeyValuePair<T, string> TypeKeyValue = new KeyValuePair<T, string>(level, Description);
_comboBoxItemSource.Add(TypeKeyValue);
}
}
return _comboBoxItemSource;
}
}
i'm trying to get my view in different languages, using Properties/Resources.resx file for localization.
I've my model looks like the following:
class City
{
public int Id { get; set; }
public string LocalizationKey { get; set; }
}
The ViewModel:
class ViewModel
{
public ObservableCollection<City> Cities { get; set; }
}
And on my View, i've the following code:
<ItemsControl ItemsSource="{Binding Cities}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding LocalizationKey}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
When i want to get the value of my string key from the dictionary for only one item without Items Collection it works correctly by using the following code:
<TextBlock Text="{x:Static properties:Resources.MyStringKey}" />
The problem is when using the code above with an ItemsControl where the keys are unknowns! Is there any way to access to the dictionary values by using the LocalizationKey as an index?
Can you do something like:
public class City
{
public int Id { get; set; }
public string LocalizationKey { get; set; }
public City(string englishName)
{
LocalizationKey = Properties.Resources.ResourceManager.GetString(englishName);
}
}
I'm not sure this best practice; but it what came to mind first.
After hours of web searching, i finally found a solution by using a converter, it may not the best practices to solve the problem but at least it does exactly what i want:
My Converter:
public class LocalizationConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var key = value as string;
if (!string.IsNullOrEmpty(key))
{
string dictionaryValue = Resources.ResourceManager.GetString(key);
return dictionaryValue ?? key;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
and the XAML code:
<TextBlock Text="{Binding LocalizationId, Converter={StaticResource LocalizationConverter}}" />
Thanks.