I have a a class which is made of property information of a target(like type and values ). Iam using this UI to show all types on view in graphical format like Enum with comboboxes and boolean with checkboxes.Everything works well for me except the case when I change combobox value in UI,it does not change in viewmodel.Every time I change value in combobox it calls convertback method in my converter.I need to convert this string to enum type,I can write the convertback code easily for a particular enum type ,but how can I convert all other enums with this converter,I have the information of Type in PropertyType property that I can pass to converter and use it but I have no idea how to do it.
This is my UI code (only relevant part)
<!-- Default DataTemplate -->
<DataTemplate x:Key="DefaultDataTemplate">
<Grid Margin="4" MinHeight="25">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<TextBox Margin="8,0" Grid.Column="1" Text="{Binding Value}" />
</Grid>
</DataTemplate>
<!-- DataTemplate for Booleans -->
<DataTemplate x:Key="BooleanDataTemplate">
<Grid Margin="4" MinHeight="25">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<CheckBox Margin="8,0" Grid.Column="1" IsChecked="{Binding Value}" />
</Grid>
</DataTemplate>
<!-- DataTemplate for Enums -->
<DataTemplate x:Key="EnumDataTemplate">
<Grid Margin="4" MinHeight="25">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="Key" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
<ComboBox Margin="8,0" SelectedItem="{Binding Value,Converter={StaticResource EnumToStringConverter},Mode=TwoWay}"
ItemsSource="{Binding PropertyType,
Converter={local:EnumToListConverter}}" Grid.Column="1"
HorizontalAlignment="Stretch" />
</Grid>
</DataTemplate>
<!-- DataTemplate Selector -->
<local:PropertyDataTemplateSelector x:Key="templateSelector"
DefaultnDataTemplate="{StaticResource DefaultDataTemplate}"
BooleanDataTemplate="{StaticResource BooleanDataTemplate}"
EnumDataTemplate="{StaticResource EnumDataTemplate}"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<ListView Grid.Row="0" ItemsSource="{Binding Model,Converter={StaticResource PropConverter}, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Grid.IsSharedSizeScope="True"
HorizontalContentAlignment="Stretch"
ItemTemplateSelector="{StaticResource templateSelector}"
/>
and my converter and view model
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string EnumString;
try
{
EnumString = Enum.GetName((value.GetType()), value);
return EnumString;
}
catch
{
return string.Empty;
}
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return null;
//What to do here
}
View model
public class PropertyValue
{
private PropertyInfo propertyInfo;
private object baseObject;
public PropertyValue(PropertyInfo propertyInfo, object baseObject)
{
this.propertyInfo = propertyInfo;
this.baseObject = baseObject;
}
public string Name
{
get { return propertyInfo.Name; }
}
public Type PropertyType { get { return propertyInfo.PropertyType; } }
public object Value
{
get { return propertyInfo.GetValue(baseObject, null); }
set
{
propertyInfo.SetValue(baseObject, value , null);
}
}
}
Try
return (targetType)Enum.Parse(typeof(targetType), value.ToString());
In ConvertBack, is targetType the correct enum type?
If so, I think this should work:
Enum.Parse(targetType, (String)value)
you shoud do it like this
enum Colors { Red = 1, Green = 2, Blue = 4, Yellow = 8 };
string colorName = "Blue";
if (Enum.IsDefined(typeof(Colors), colorName)) //true
{
Colors clr = (Colors)Enum.Parse(typeof(Colors), colorName);
}
colorName = "Orange";
if (Enum.IsDefined(typeof(Colors), colorName)) //false
{
Colors clr = (Colors)Enum.Parse(typeof(Colors), colorName);
}
I dont think its feasible to do this with converter or with multibinding.I solved my problem by not using the converter for combobox and checking value in getter and setter.
I modified my class which holds the property info and it works now,I am putting a special check for enum type ,rest of the types I can use converters.
public class PropertyValue
{
private PropertyInfo propertyInfo;
private object baseObject;
public PropertyValue(PropertyInfo propertyInfo, object baseObject)
{
this.propertyInfo = propertyInfo;
this.baseObject = baseObject;
}
public string Name
{
get { return propertyInfo.Name; }
}
public Type PropertyType
{
get { return propertyInfo.PropertyType; }
}
public object Value
{
get
{
var retVal = propertyInfo.GetValue(baseObject, null);
if (PropertyType.IsEnum)
{
retVal = retVal.ToString();
}
return retVal;
}
set
{
if (PropertyType.IsEnum)
{
value = Enum.Parse(propertyInfo.PropertyType, value.ToString());
}
propertyInfo.SetValue(baseObject, value, null);
}
}
}
I dont like corrupting view model for that but I cant see any option at this moment.Please let me know if there are any risks in my code or if you have a better approach.
What I could think quickly is that you can create one class EnumDescriptor with just two properties: EnumString (string) and EnumType (Type)
Your "EnumToListConverter" can return list of EnumDescriptor and you can bind display value of combobox to EnumString property.
In your "EnumToStringConverter", in Convert() you can create instance of EnumDescriptor as we have type and name of enum there, in ConvertBack() you will get the instance of EnumDescriptor from where you can parse the enum value and return it.
Thanks
Related
I try to define constants for string values whenever necessary to avoid burying "magic strings" in the code. I recently found myself needing to define a custom format string for a converter, so I defined public const string MyFormat = "a"; in the converter. When using the converter, I need to pass in "a" as the ConverterParameter, but rather than typing ConverterParameter=a, I would like to refer to the named constant.
<TextBlock Text="{x:Bind SomeProperty, Converter={StaticResource MyConverter}, ConverterParameter=???}" />
I've tried ConverterParameter=local:MyConverter.MyFormat, but it just passes in the string "local:MyConverter.MyFormat" rather than "a".
I've tried ConverterParameter={x:Bind local:MyConverter.MyFormat} but I get an error Nested x:Bind expressions are not supported.
I've tried ConverterParameter={StaticResource MyFormat} and adding it as a resource, but that just moves the problem and introduces a new problem:
I don't know how to reference the constant in the resources either. I can add <x:String x:Key="MyFormat">a</x:String>, but then I've defined the constant in two different places and if it changes I have to remember to update it in two different places.
Even if I do this, the string is not passed as the parameter as expected. The parameter is null.
Is there any way to refer to a named constant in XAML? (Note: this is UWP, not WPF.)
Here's a simple repro:
MainPage.xaml
<Page
x:Class="XamlConstantReference.MainPage"
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="using:XamlConstantReference"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Page.Resources>
<local:MyConverter x:Key="MyConverter" />
<!-- How do I refer to MyConverter.MyFormat rather than defining it a second time? -->
<x:String x:Key="MyFormat">a</x:String>
</Page.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<!-- How do I refer to MyConverter.MyFormat rather than passing in ConverterParameter=a? -->
<TextBlock Grid.Row="0" Text="{x:Bind SomeProperty, Converter={StaticResource MyConverter}, ConverterParameter=a}" />
<TextBlock Grid.Row="1" Text="{x:Bind SomeProperty, Converter={StaticResource MyConverter}, ConverterParameter=local:MyConverter.MyFormat}" />
<!-- <TextBlock Grid.Row="2" Text="{x:Bind SomeProperty, Converter={StaticResource MyConverter}, ConverterParameter={x:Bind local:MyConverter.MyFormat}}" /> -->
<TextBlock Grid.Row="3" Text="{x:Bind SomeProperty, Converter={StaticResource MyConverter}, ConverterParameter={StaticResource MyFormat}}" />
</Grid>
</Page>
MainPage.xaml.cs
using System.ComponentModel;
using Windows.UI.Xaml.Controls;
namespace XamlConstantReference
{
public sealed partial class MainPage : Page, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _someProperty = "Hello World!";
public string SomeProperty
{
get => _someProperty;
set
{
if (_someProperty != value)
{
_someProperty = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SomeProperty)));
}
}
}
public MainPage()
{
InitializeComponent();
}
}
}
MyConverter.cs
using System;
using Windows.UI.Xaml.Data;
namespace XamlConstantReference
{
public class MyConverter : IValueConverter
{
public const string MyFormat = "a";
public object Convert(object value, Type targetType, object parameter, string language)
{
if (parameter == null)
{
return $"Fail. Formatted '{value}' with null parameter.";
}
else if (parameter is string parameterString)
{
switch (parameterString)
{
case MyFormat:
return $"Success! Formatted '{value}' with parameter '{parameterString}'.";
default:
return $"Fail. Formatted '{value}' with parameter '{parameterString}'.";
}
}
throw new ArgumentException();
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}
I'm creating a WPF application(UI) with a TabControl which contains four TabItems in it.
My Application --
From the User Tab, I want to choose somehow (maybe with a check box, or any other way) which of the GridColumns will be displayed at the User Tab. I can work with the other Tabs, but sometimes I need to give the user the opportunity to work only with the specific outputs he/she wants. How can I make this work? I am new to C# and wpf, so if you could explain a simple solution and offer some codes, I would appriciate it.
Before answering your question, a brief hint for you: when you ask, post some code, otherwise it will be hard that someone spends time for helping you.
For implementing your application you need to consider:
ElementName data binding
ElementName data binding can't work in the ColumnDefinitions property
To solve the point number 2 you can read this very interesting article by Josh Smith.
Pratically he creates a special kind of ElementSpy with an attached property:
public class ElementSpy : Freezable
{
private DependencyObject element;
public static ElementSpy GetNameScopeSource(DependencyObject obj)
{
return (ElementSpy)obj.GetValue(NameScopeSourceProperty);
}
public static void SetNameScopeSource(DependencyObject obj, ElementSpy value)
{
obj.SetValue(NameScopeSourceProperty, value);
}
public static readonly DependencyProperty NameScopeSourceProperty =
DependencyProperty.RegisterAttached("NameScopeSource", typeof(ElementSpy), typeof(ElementSpy),
new UIPropertyMetadata(null, OnNameScopeSourceProperty));
private static void OnNameScopeSourceProperty(DependencyObject d, DependencyPropertyChangedEventArgs args)
{
INameScope nameScope;
ElementSpy elementSpy = args.NewValue as ElementSpy;
if (elementSpy != null && elementSpy.Element != null)
{
nameScope = NameScope.GetNameScope(elementSpy.Element);
if (nameScope != null)
{
d.Dispatcher.BeginInvoke(new Action<DependencyObject, INameScope>(SetScope),
System.Windows.Threading.DispatcherPriority.Normal,
d, nameScope);
}
}
}
private static void SetScope(DependencyObject d, INameScope nameScope)
{
NameScope.SetNameScope(d, nameScope);
}
public DependencyObject Element
{
get
{
if (element == null)
{
PropertyInfo propertyInfo = typeof(Freezable).GetProperty("InheritanceContext",
BindingFlags.NonPublic | BindingFlags.Instance);
element = propertyInfo.GetValue(this, null) as DependencyObject;
if (element != null)
{
Freeze();
}
}
return element;
}
}
protected override Freezable CreateInstanceCore()
{
return new ElementSpy();
}
}
Now we can use binding in our XAML
<Window.Resources>
<local:BooleanWidthConverter x:Key="BooleanWidthConverter" />
<local:ElementSpy x:Key="ElementSpy" />
</Window.Resources>
<StackPanel HorizontalAlignment="Stretch">
<Grid local:ElementSpy.NameScopeSource="{StaticResource ElementSpy}"
Margin="0,0,0,30">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding ElementName=cb1, Path=IsChecked, Converter={StaticResource BooleanWidthConverter}, Mode=OneWay}" />
<ColumnDefinition Width="{Binding ElementName=cb2, Path=IsChecked, Converter={StaticResource BooleanWidthConverter}, Mode=OneWay}" />
<ColumnDefinition Width="{Binding ElementName=cb3, Path=IsChecked, Converter={StaticResource BooleanWidthConverter}, Mode=OneWay}" />
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Center" Text="Column 1" Margin="4" Grid.Column="0" />
<TextBlock HorizontalAlignment="Center" Text="Column 2" Margin="4" Grid.Column="1" />
<TextBlock HorizontalAlignment="Center" Text="Column 3" Margin="4" Grid.Column="2" />
</Grid>
<CheckBox x:Name="cb1" Content="Column 1" Margin="4" IsChecked="true" HorizontalAlignment="Center" />
<CheckBox x:Name="cb2" Content="Column 2" Margin="4" IsChecked="true" HorizontalAlignment="Center" />
<CheckBox x:Name="cb3" Content="Column 3" Margin="4" IsChecked="true" HorizontalAlignment="Center" />
</StackPanel>
To complete our code, we need a simple converter:
public class BooleanWidthConverter : IValueConverter
{
private static GridLength star = new GridLength(1, GridUnitType.Star);
private static GridLength zero = new GridLength(0, GridUnitType.Pixel);
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool boolValue = (bool)value;
return boolValue ? star : zero;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Of course this is just a sample prototype, but I am sure it can help you with your application.
I have a Views/Doc.xaml with:
<navigation:Page ....
<data:DataGrid>
<data:DataGridTemplateColumn Header="Actions" HeaderStyle="{StaticResource TextHeaderStyle}" >
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid x:Name="gridDocumentColumns">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<HyperlinkButton x:Name="hlEmail" Grid.Column="1" Tag="{Binding Index}" Click="hlEmail_Click" >
<ToolTipService.ToolTip>
<ToolTip Tag="ToolTipEmail" Opened="toolTip_ActionOpened" />
</ToolTipService.ToolTip>
<Image Source="../images/close.png" Stretch="None" />
</HyperlinkButton>
</Grid>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid>
..............
I have a second class Views/Controls/ReadDocs.xaml (). If a certain condition in ReadDocs.xaml Code Behind is true i want to change the image source in Views/Doc.xaml to ../images/open.png
How can i achieve this?
You can define a converter and pass a flag value to it.
This converter will returns a specific path depending upon the value you have passed.
Kindly refer the below converter for reference...
public sealed class ImagetoPathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null)
{
return value = "ms-appx:///Assets/Images/bk/1.png";
}
else if (value.ToString() == "1")
{
return value = "ms-appx:///Assets/Images/bk/2.png";
}
else if (value.ToString() == "2")
{
return value = "ms-appx:///Assets/Images/bk/3.png";
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Bind the converter in Source and pass your conditional value.
I have a ComboBox Binded to a List of Possible values is the ViewModel.
<ComboBox ItemsSource="{Binding PossibleOperands}" SelectedValue="{Binding Operand, Mode=TwoWay}" VerticalAlignment="Center" Foreground="Black" FontSize="13" FontFamily="Calibri" Height="23" Grid.Column="1" Margin="7,2,0,2"/>
For most values I want the display a simple string (in The example above, "Operand Name"),
but for one of the values, I want to display a string + 2 comboBoxes.
Mockup:
Use ItemTemplateSelector class as described in MSDN
You can define a single ItemTemplate with a textblock and 2 combobox. Bind the Visibility of ComboBoxes with the required property. This way only for certain items the ComboBoxes will be visible.
I have done something like that before:
Assuming you have a class like this: (for the ComboBox's Content)
public class Content
{
private String _Texty;
public String Texty
{
get{return _Texty;}
set { _Texty = value; }
}
public List<String> Comb1{get ; set;}
public List<String> Comb2 { get; set; }
public Content(string t, List<String> comb1, List<String> comb2)
{
Texty = "Some Text";
Comb1 = comb1;
Comb2 = comb2;
}
}
You will need a Converter, defined in your xaml part like this:
<utils:ContentToVisibleConverter x:Key="MyConverter" />
where utils is something like:
xmlns:utils="clr-namespace:YOUR CONVERTER CLASS NAMESPACE"
So, define your Combobox like this in the xaml:
<ComboBox x:Name="Combo" Margin="4"
Height="23"
Width="250" ItemsSource="{Binding Collection}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<TextBlock Text="{Binding Texty}" Margin="4,0"/>
<Grid Margin="20 0 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Entity Type:"
Visibility="{Binding Path=Comb1, Converter={StaticResource MyConverter}}" />
<ComboBox Grid.Column="1" ItemsSource="{Binding Comb1}" Width="80" Margin="4,0"
Visibility="{Binding Path=Comb1, Converter={StaticResource MyConverter}}"/>
<TextBlock Grid.Column="2" Text="Entity:"
Visibility="{Binding Path=Comb2, Converter={StaticResource MyConverter}}"/>
<ComboBox Grid.Column="3" ItemsSource="{Binding Comb2}" Width="80" Margin="4,0"
Visibility="{Binding Path=Comb2, Converter={StaticResource MyConverter}}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Somewhere in my code, I use a Binding Collection this way:
private ObservableCollection<Content> _Collection;
public ObservableCollection<Content> Collection
{
get { return _Collection; }
set { _Collection = value; NotifyPropertyChanged("Collection"); }
}
// Fill it like this:
Collection = new ObservableCollection<Content>();
Collection.Add(new Content("Some Stuff", null, null));
Collection.Add(new Content("Some Stuff", null, null));
Collection.Add(new Content("Some Stuff", null, null));
Collection.Add(new Content("Some Stuff",
new List<String>() { "One", "Two" },
new List<String>() { "One", "Two" }));
Finally, I define a class for the converter it only contains:
public class ContentToVisibleConverter : System.Windows.Markup.MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var s = value as List<string>;
if (s != null)
return s.Count > 0 ? Visibility.Visible : Visibility.Collapsed;
else return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
That's all, let me know if it worked for you.
I have a datagrid with grouping and I am trying to style the template to add in some summary about the group.
IN XAML DataGrid.RowGroupHeaderStyle
<Grid Grid.Column="3" Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Stretch" Margin="0,1,0,1" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Name}" Margin="4,0,0,0" />
<TextBlock Grid.Column="1" Text="{Binding Items, Converter="{StaticResource summaryConverter}"}" />
</Grid>
Binding of data grid items source
PagedCollectionView collection = new PagedCollectionView(e.Result.ToList<MyClass>());
collection.GroupDescriptions.Add(new PropertyGroupDescription("Name"));
dataGrid.ItemsSource = collection;
MyClass
class MyClass {
public string Name;
public double Value;
}
I have created a converter to grab the Items of the same group but I am facing problem in converting the object into List<MyClass>(). I receive this error
Unable to cast object of type 'System.Collections.ObjectModel.ReadOnlyObservableCollection1[System.Object]' to type 'System.Collections.Generic.List1[MyClass]'.
`
In Converter.cs
public object Convert(object values, Type targetType, object param, CultureInfo culture) {
var source = (List<MyClass>)values;
}
Does anyone know how should I do the conversion??
Use LINQ.
public object Convert(object values, Type targetType, object param, CultureInfo culture) {
var source = (ReadOnlyObservableCollection<object>) values;
List<MyClass> list = source.OfType<MyClass>().ToList();
...
}