I have two constants defined in XAML and I would like to define the third based on that two:
<UserControl ... xmlns:clr="clr-namespace:System;assembly=mscorlib">
<UserControl.Resources>
<clr:Int32 x:Key="Constant1">1</clr:Int32>
<clr:Int32 x:Key="Constant2">2</clr:Int32>
<!-- Is it possible to achieve something like this? -->
<clr:Int32 x:Key="Constant3">{StaticResource Constant1} + {StaticResource Constant2}</clr:Int32>
</UserControl.Resources>
</UserControl>
Is it possible?
It is possible, but not as you want to do it.
One solution I can think of is a custom MarkupExtension like this:
[MarkupExtensionReturnType(typeof(int))]
public class IntCalculator:MarkupExtension
{
public List<int> Values { get; set; }
public List<string> ResourceNames { get; set; }
public IntCalculator()
{
Values = new List<int>();
ResourceNames = new List<string>();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var root = (IRootObjectProvider)serviceProvider.GetService(typeof(IRootObjectProvider));
var rootObject = root.RootObject as FrameworkElement;
int calcVal = 0;
if (rootObject != null)
{
foreach (var resourceName in ResourceNames)
{
var resource = rootObject.FindResource(resourceName);
if (resource != null && resource is int)
{
calcVal += System.Convert.ToInt32(resource);
}
}
}
foreach (var value in Values)
{
calcVal += value;
}
return calcVal;
}
}
With this Extension you can add int resources or int values.
This is how to use it:
in your resources:
<local:IntCalculator x:Key="CalcVal">
<local:IntCalculator.ResourceNames>
<clr:String>Constant1</clr:String>
<clr:String>Constant2</clr:String>
</local:IntCalculator.ResourceNames>
</local:IntCalculator>
to display the value:
<Label Content="{StaticResource CalcVal}"/>
Related
CheckList Box from WPFToolKit. Below is XAML code (MainWindow.xaml)
<xctk:CheckListBox x:Name="SiteCheckList" Margin="0,0,512,0" Height="100" Width="150"
ItemsSource="{Binding SiteList}"
DisplayMemberPath="SiteName"
CheckedMemberPath="IsChecked">
</xctk:CheckListBox>
Below Properties added in Model Class. I would like to get Checked Items from CheckListBox to my string List (Model.cs).
This string List I will be using for further in project logic.
private string _SiteName;
public string SiteName
{
get { return _SiteName; }
set { _SiteName = value; }
}
private List<string> _SelectedSiteList;
public List<string> SelectedSiteList
{
get { return _SelectedSiteList; }
set
{
_SelectedSiteList = value;
}
}
View Model (ViewModel.cs)
class ViewModel : INotifyPropertyChanged
{
private ObservableCollection<Model> _SiteList;
public ObservableCollection<DataModel> SiteList
{
get { return _SiteList; }
set { _SiteList = value; }
}
public ViewModel()
{
SiteList = new ObservableCollection<Model>();
PoppulateSiteNames();
}
private void PoppulateSiteNames()
{
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
keyValuePairs = Files.ReadIni_KeyValue("SiteSection");
foreach (string Key in keyValuePairs.Keys)
{
keyValuePairs.TryGetValue(Key, out string LogTable);
SiteList.Add(new Model() { SiteName = LogTable });
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string PropertyName)
{
if (PropertyChanged !=null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
Here I would like to get list of Checked / Selected Items from UI. If I don't want to write any code in MainWindow.cs i.e. CheckedChanged event then How I can do it by using Binding method ?
Updated Model Class with IsChecked Boolean Property.
private bool _IsChecked;
public bool IsChecked
{
get { return _IsChecked; }
set { _IsChecked = value; }
}
Then ViewModel Updated with Below Function to Populate SiteList
private void PoppulateSiteNames()
{
Dictionary<string, string> keyValuePairs = Files.ReadIni_KeyValue(Vars.MSSQL_Section);
string [] Sites = keyValuePairs.Values.ToArray();
for (int i = 0; i < Sites.Length; i++)
{
SiteList.Add(new HistoricalDataModel { SiteName = Sites[i] });
}
}
Now, Finally I got CheckedItems in below Variable using LINQ
var checkedSites = from site in SiteList
where (site.IsChecked == true)
select new { site.SiteName };
Thank you all of you for responding my question and triggering me to think more.
Hi am struggling with validation with data annotations in WPF
I have a class in a separate project because we are reusing the class across multiple projects.Several of the fields have data annotations for validation.
the validation class combines the errors returned from data annotations with some custom business rules which are different per client
internal class Validation<T> : ValidationRule where T : class, ICore
{
IUnitofWork unitofWork;
List<Func<T, bool>> CompiledRules = new List<Func<T, bool>>();
List<Rule<T>> Rules = new List<Rule<T>>();
Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
List<Func<Object, bool>> FieldRules = new List<Func<object, bool>>();
public Validation()
{
unitofWork = new UnitOfWork();
CompileRules();
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
return IsValid((T)value);
}
public ValidationResult Validate(object value,string PropertyName ,CultureInfo cultureInfo)
{
return IsValid((T)value,PropertyName);
}
void CompileRules()
{
Type t = typeof(T);
List<BusinessRule> rules = unitofWork.Repository<BusinessRule>().GetAll(b => b.Name == t.Name && b.Enabled == true);
foreach (BusinessRule b in rules)
{
Func<T, bool> CompiledRule = CompileRule(b);
CompiledRules.Add(CompiledRule);
Rule<T> rule = new Rule<T>();
rule.CompiledRule = CompiledRule;
rule.ErrorMessage = b.MessageTemplate;
rule.FieldName = b.Field;
Rules.Add(rule);
}
}
ValidationResult IsValid(T Value)
{
bool valid = true;
_errors.Clear();
if (CompiledRules.Count > 0 || Value.Errors.Count > 0)
{
//valid = CompiledRules.All(rule => rule(Value));
foreach (Rule<T> r in Rules)
{
bool isValid = r.CompiledRule(Value);
r.PassedRule = isValid;
string field = r.FieldName;
if (!isValid )
{
valid = false;
string ErrorMessage = string.Format("{0} {1}", r.FieldName, r.ErrorMessage);
if (_errors.ContainsKey(field))
_errors[field].Add(ErrorMessage);
else
{
List<string> el = new List<string>();
el.Add(ErrorMessage);
_errors.Add(field, el);
}
}
}
}
ValidateAnnotations(Value,ref valid);
return new ValidationResult(valid, _errors);
}
void ValidateAnnotations(object Value,ref bool Valid)
{
DataAnnotationValidator annotationValidator = new DataAnnotationValidator();
List< System.ComponentModel.DataAnnotations.ValidationResult> results = annotationValidator.ValidateObject(Value);
if (results.Count > 0)
{
Valid = false;
foreach (System.ComponentModel.DataAnnotations.ValidationResult r in results)
{
if (_errors.ContainsKey(r.MemberNames.First()))
{
_errors[r.MemberNames.First()].Add(r.ErrorMessage);
}
else
{
List<string> propErrors = new List<string>();
propErrors.Add(r.ErrorMessage);
_errors.Add(r.MemberNames.First(), propErrors);
}
}
}
}
void ValidateAnnotations(object Value,string PropertyName, ref bool Valid)
{
DataAnnotationValidator annotationValidator = new DataAnnotationValidator();
List<System.ComponentModel.DataAnnotations.ValidationResult> results = annotationValidator.ValidateObject(Value, PropertyName);
if (results.Count > 0)
{
Valid = false;
foreach (System.ComponentModel.DataAnnotations.ValidationResult r in results)
{
if (_errors.ContainsKey(r.MemberNames.First()))
{
_errors[r.MemberNames.First()].Add(r.ErrorMessage);
}
else
{
List<string> propErrors = new List<string>();
propErrors.Add(r.ErrorMessage);
_errors.Add(r.MemberNames.First(), propErrors);
}
}
}
}
ValidationResult IsValid(T Value, string PropertyName)
{
_errors.Remove(PropertyName);
bool valid = true;
if (CompiledRules.Count > 0)
{
//valid = CompiledRules.All(rule => rule(Value));
foreach (Rule<T> r in Rules.Where(b=>b.FieldName == PropertyName))
{
bool isValid = r.CompiledRule(Value);
r.PassedRule = isValid;
string field = r.FieldName;
// string field = "SelectedRow." + r.FieldName;
if (!isValid)
{
valid = false;
string ErrorMessage = string.Format("{0} {1}", r.FieldName, r.ErrorMessage);
if (_errors.ContainsKey(field))
_errors[field].Add(ErrorMessage);
else
{
List<string> el = new List<string>();
el.Add(ErrorMessage);
_errors.Add(field, el);
}
}
}
}
ValidateAnnotations(Value,PropertyName, ref valid);
return new ValidationResult(valid, _errors);
}
public Func<T, bool> CompileRule(BusinessRule r)
{
var paramT = Expression.Parameter(typeof(T));
Expression expression = BuildExpr(r, paramT);
return Expression.Lambda<Func<T, bool>>(expression, paramT).Compile();
}
static Expression BuildExpr(BusinessRule r, ParameterExpression param)
{
var left = MemberExpression.Property(param, r.Field);
var tProp = typeof(T).GetProperty(r.Field).PropertyType;
ExpressionType tBinary;
// is the operator a known .NET operator?
if (ExpressionType.TryParse(r.Operator, out tBinary))
{
var right = Expression.Constant(Convert.ChangeType(r.CompareValue, tProp));
// use a binary operation, e.g. 'Equal' -> 'u.Age == 15'
return Expression.MakeBinary(tBinary, left, right);
}
else
{
var method = tProp.GetMethod(r.Operator);
var tParam = method.GetParameters()[0].ParameterType;
var right = Expression.Constant(Convert.ChangeType(r.CompareValue, tParam));
// use a method call, e.g. 'Contains' -> 'u.Tags.Contains(some_tag)'
return Expression.Call(left, method, right);
}
}
}
internal class Rule<T> where T : class, ICore
{
public string FieldName
{ get;set; }
public Func<T, bool> CompiledRule
{ get; set; }
public Func<object,bool> RevisedRule
{ get; set; }
public string ErrorMessage
{ get; set; }
public bool PassedRule
{ get; set; }
}
internal class Error
{
public string ErrorMessage { get; set; }
public string FieldName { get; set; }
}
The is working as the _errors list has the field names and any errors associated with them.
once we have these we loop through and raise the onPropertyErrorsChangedEvent
internal void SetErrorDetails(ValidationResult Result)
{
propErrors = (Dictionary<string, List<string>>)Result.ErrorContent;
foreach (string key in propErrors.Keys)
{
OnPropertyErrorsChanged(key);
}
}
on my view the 2 fields are
<TextBox Canvas.Left="138"
Canvas.Top="75"
FontFamily="Verdana"
HorizontalAlignment="Left"
Height="20"
Text="{Binding OrganizationName,ValidatesOnDataErrors=True,NotifyOnValidationError=True,ValidatesOnNotifyDataErrors=True,ValidatesOnExceptions=True}" VerticalAlignment="Top" Width="137"
Validation.ErrorTemplate="{StaticResource ValidationTemplate }"
Style="{StaticResource TextErrorStyle}"/>
<TextBox Canvas.Left="138"
Canvas.Top="225"
FontFamily="Verdana"
HorizontalAlignment="Left"
Height="20"
Text="{Binding SelectedRow.Postcode,ValidatesOnDataErrors=True,ValidatesOnNotifyDataErrors=True,ValidatesOnExceptions=True}"
VerticalAlignment="Top"
Width="137"
Validation.ErrorTemplate="{StaticResource ValidationTemplate }"
Style="{StaticResource TextErrorStyle}" />
I am encountering 2 problems i am encountering
When bound directly to the selectedrow (Postcode) my data annotations appear when the form is loaded but when bound via a field on my view model (Organisation Name) they do not . We need to bind these to fields on the view model so that the business rules get run as part of validation.
Second problem if i save the form with an invalid entry for the organisation the save stops because it is invalid however i don't get an error notification even though there is an error for the property in the _errors.
I am not sure what i am doing wrong could someone point me in the right direction please?
[Edit]
We use a third party document service to create and show the view
void CreateDocument(object Arg)
{
string title = string.Empty;
if (Arg.ToString().ToLower() == "edit" && SelectedRow !=null)
{
if (SelectedRow.OrganizationName != null)
title = SelectedRow.OrganizationName;
}
else
{
SelectedRow = new Address();
title = "New Address";
}
AddressDetailVM detail = new AddressDetailVM(SelectedRow,this);
Document = iInternal.CreateDocument("AddressDetails",
detail,
title
);
detail.Document = Document;
// Document = iInternal.CreateDocument("AddressDetails", null, this, title);
Document.Show();
}
as the topic suggests I wan't to modify the Content of the CollectionEditorPicker. This control is used to open the floating Window for the List of nested Properties.
Unfortunally the RadPropertyGrid don't show any Information about the collection in the Field.
How can I set some value in there? For example a placeholder like "Click here to open the collection" or "xx Items" or "Item 1, Item 2, Item 3..." so see some preview or Information about the field.
I've tried it with a template Selector, but if I'm doing so, the opened Popup is not resizable anymore. Also it looses some Information which are in the default CollectionEditorPicker.
Can you help me?
Below a minimal working Example.
The XAML:
<Window x:Class="TelerikPropertyGridTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:model="clr-namespace:TelerikPropertyGridTest.Model"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<model:TemplateSelector x:Key="RadPropertyListTemplateSelector">
<!-- Not Working -->
<model:TemplateSelector.CollectionsDataTemplate>
<DataTemplate>
<telerik:RadDropDownButton Content="Test">
<telerik:RadDropDownButton.DropDownContent>
<telerik:CollectionEditor telerik:AutoBindBehavior.UpdateBindingOnElementLoaded="Source"
></telerik:CollectionEditor>
</telerik:RadDropDownButton.DropDownContent>
</telerik:RadDropDownButton>
</DataTemplate>
</model:TemplateSelector.CollectionsDataTemplate>
<model:TemplateSelector.FloatNumberTemplate>
<DataTemplate>
<telerik:RadNumericUpDown telerik:AutoBindBehavior.UpdateBindingOnElementLoaded="Value" />
</DataTemplate>
</model:TemplateSelector.FloatNumberTemplate>
<model:TemplateSelector.IntNumberTemplate>
<DataTemplate>
<telerik:RadNumericUpDown telerik:AutoBindBehavior.UpdateBindingOnElementLoaded="Value"
NumberDecimalDigits="0" />
</DataTemplate>
</model:TemplateSelector.IntNumberTemplate>
</model:TemplateSelector>
</Grid.Resources>
<telerik:RadPropertyGrid Item="{Binding ObjectToBind}"
AutoGeneratingPropertyDefinition="RadPropertyGrid_OnAutoGeneratingPropertyDefinition"
EditorTemplateSelector="{StaticResource RadPropertyListTemplateSelector}">
</telerik:RadPropertyGrid>
</Grid>
</Window>
The ViewModel (generates a Random Object for testing)
public class MainWindowViewModel : BindableBase
{
private readonly Random _random = new Random();
private IExampleInterface _objectToBind;
public MainWindowViewModel()
{
this.ObjectToBind = new ExampleImplementation
{
SomeBooleanValue = this._random.Next() % 2 == 1,
SomeDateValue = this.RandomDay(),
SomeIntValue = this._random.Next(),
SomeString = Guid.NewGuid().ToString(),
SubClasses = new List<IExampleInterface>
{
new ExampleImplementation
{
SomeBooleanValue = this._random.Next() % 2 == 1,
SomeDateValue = this.RandomDay(),
SomeIntValue = this._random.Next(),
SomeString = Guid.NewGuid().ToString(),
SubClasses = new List<IExampleInterface>
{
new ExampleImplementation
{
SomeBooleanValue =
this._random.Next() % 2 == 1,
SomeDateValue = this.RandomDay(),
SomeIntValue = this._random.Next(),
SomeString = Guid.NewGuid().ToString()
}
}
}
}
};
}
public IExampleInterface ObjectToBind
{
get { return this._objectToBind; }
set
{
if (this._objectToBind != value)
{
this._objectToBind = value;
this.OnPropertyChanged("ObjectToBind");
}
}
}
private DateTime RandomDay()
{
var start = new DateTime(1995, 1, 1);
var range = (DateTime.Today - start).Days;
return start.AddDays(this._random.Next(range));
}
}
The IExampleInterface (should be later on a real Interface):
public interface IExampleInterface
{
string SomeString { get; set; }
int SomeIntValue { get; set; }
double SomeDouble { get; set; }
IList<IExampleInterface> SubClasses { get; set; }
IList<IExampleInterface> SubClasses2 { get; set; }
bool SomeBooleanValue { get; set; }
DateTime SomeDateValue { get; set; }
SomeEnum SomeEnumValue { get; set; }
}
The ExampleImplementation (should have later on a Real Implementation with additional Properties).
public class ExampleImplementation : BindableBase, IExampleInterface
{
private bool _someBooleanValue;
private DateTime _someDateValue;
private double _someDouble;
private SomeEnum _someEnumValue;
private int _someIntValue;
private string _someString;
private ObservableCollection<IExampleInterface> _subClasses;
private ObservableCollection<IExampleInterface> _subClasses2;
public bool SomeBooleanValue
{
get { return this._someBooleanValue; }
set
{
if (this._someBooleanValue != value)
{
this._someBooleanValue = value;
this.OnPropertyChanged("SomeBooleanValue");
}
}
}
public DateTime SomeDateValue
{
get { return this._someDateValue; }
set
{
if (this._someDateValue != value)
{
this._someDateValue = value;
this.OnPropertyChanged("SomeDateValue");
}
}
}
public double SomeDouble
{
get { return this._someDouble; }
set
{
if (Math.Abs(this._someDouble - value) > 0.01)
{
this._someDouble = value;
this.OnPropertyChanged("SomeDouble");
}
}
}
public SomeEnum SomeEnumValue
{
get { return this._someEnumValue; }
set
{
if (this._someEnumValue != value)
{
this._someEnumValue = value;
this.OnPropertyChanged("SomeEnumValue");
}
}
}
public int SomeIntValue
{
get { return this._someIntValue; }
set
{
if (this._someIntValue != value)
{
this._someIntValue = value;
this.OnPropertyChanged("SomeIntValue");
}
}
}
[Display(Name = #"TestString", GroupName = #"TestGroup", Description = #"TestDescription")]
public string SomeString
{
get { return this._someString; }
set
{
if (this._someString != value)
{
this._someString = value;
this.OnPropertyChanged("SomeString");
}
}
}
[Display(Name = #"Some Subclasses")]
public IList<IExampleInterface> SubClasses
{
get { return this._subClasses; }
set
{
if (!Equals(this._subClasses, value))
{
this._subClasses = new ObservableCollection<IExampleInterface>(value);
this.OnPropertyChanged("SubClasses");
}
}
}
public IList<IExampleInterface> SubClasses2
{
get { return this._subClasses2; }
set
{
if (!Equals(this._subClasses2, value))
{
this._subClasses2 = new ObservableCollection<IExampleInterface>(value);
this.OnPropertyChanged("SubClasses2");
}
}
}
}
And finally the TemplateSelector
public class TemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var def = item as PropertyDefinition;
if (def == null || def.SourceProperty == null)
{
return base.SelectTemplate(item, container);
}
if (typeof (IEnumerable).IsAssignableFrom(def.SourceProperty.PropertyType) && typeof(string) != def.SourceProperty.PropertyType)
{
return this.CollectionsDataTemplate;
}
if (typeof (double).IsAssignableFrom(def.SourceProperty.PropertyType))
{
return this.FloatNumberTemplate;
}
if (typeof (int).IsAssignableFrom(def.SourceProperty.PropertyType))
{
return this.IntNumberTemplate;
}
return base.SelectTemplate(item, container);
}
public DataTemplate CollectionsDataTemplate { get; set; }
public DataTemplate FloatNumberTemplate { get; set; }
public DataTemplate IntNumberTemplate { get; set; }
}
This is what I expect
The optimal solution would be to get detailed Information in the TextBlock, like Item 1, item 2 etc.
Thank you.
// Edit:
I've figured out the NullReferenceException and got a Demo to work, so that I can modify the text. But the popup is different to the default. Have you an idea to fix it?
I've updated the text and the example.
After wasting a few hours now I figured out a solution to realize this.
I've added a custom behavior to the Collection template. This behavior sets the Header of the CollectionEditor as soon as it's loaded or updated.
Below you can see my modifications:
The Template:
<model:TemplateSelector.CollectionsDataTemplate>
<DataTemplate>
<telerik:RadDropDownButton Content="Click to edit the collection">
<telerik:RadDropDownButton.DropDownContent>
<telerik:CollectionEditor telerik:AutoBindBehavior.UpdateBindingOnElementLoaded="Source"
ResizeGripperVisibility="Visible">
<i:Interaction.Behaviors>
<model:CollectionEditorBehavior />
</i:Interaction.Behaviors>
</telerik:CollectionEditor>
</telerik:RadDropDownButton.DropDownContent>
</telerik:RadDropDownButton>
</DataTemplate>
</model:TemplateSelector.CollectionsDataTemplate>
The behavior:
internal class CollectionEditorBehavior : Behavior<CollectionEditor>
{
protected override void OnAttached()
{
this.AssociatedObject.SourceUpdated += (sender, args) => this.PrepareHeader();
this.AssociatedObject.DataContextChanged += (sender, args) => this.PrepareHeader();
this.AssociatedObject.Loaded += (sender, args) => this.PrepareHeader();
}
private void PrepareHeader()
{
if (this.AssociatedObject == null)
{
// Error Case
return;
}
if (this.AssociatedObject.CollectionView == null ||
this.AssociatedObject.CollectionView.SourceCollection == null)
{
// Source not set
this.AssociatedObject.Header = "Collection";
return;
}
// Get the property from the DataContext to retrieve HeaderInformation
var propInfo = this.AssociatedObject.DataContext
.GetType()
.GetProperties()
.FirstOrDefault(
propertyInfo =>
Equals(propertyInfo.GetValue(this.AssociatedObject.DataContext),
this.AssociatedObject.CollectionView.SourceCollection));
if (propInfo == null)
{
// We didn't got the property Information, using default value
this.AssociatedObject.Header = "Collection";
return;
}
// Getting the DisplayName Attribute
var attr = Attribute.GetCustomAttribute(propInfo,
typeof (DisplayNameAttribute)) as DisplayNameAttribute;
if (attr != null)
{
// We have a DisplayName attribute
this.AssociatedObject.Header = attr.DisplayName;
return;
}
// Alternative: Get the Display Attribute
var attr2 = Attribute.GetCustomAttribute(propInfo,
typeof (DisplayAttribute)) as DisplayAttribute;
if (attr2 != null)
{
// We have the Display Attribute
this.AssociatedObject.Header = attr2.Name;
return;
}
// We have no DisplayAttribute and no DisplayName attribute, set it to the PropertyName
this.AssociatedObject.Header = propInfo.Name;
}
}
I think I am missing something obvious. But since the main window of my Application is a UserControl that is being launched by
protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<MainWindowViewModel>();
}
in my bootstrapper how do I set the Icon of the window itself and of the application in the toolbar?
XAML based solution: Change your MainWindowView base class from UserControl to Window (both in .xaml and in .xaml.cs), then set your Icon property or any other window-specific properties right in xaml.
Code based solution: DisplayRootViewFor<T> takes an optional settings parameter:
var settings = new Dictionary<string, object>
{
{ "Icon", new BitmapImage(new Uri("pack://application:,,,/WpfApplication2;component/icon.png")) },
{ "ResizeMode", ResizeMode.NoResize }
};
DisplayRootViewFor<IShell>(settings);
The keys should correspond to the window properties you want to set, and the value types have to match.
//default settings for windowmanager.createwindow
public interface IPropertyKeyValue
{
string Key { get; }
object Value { get; }
}
public class PropertyKeyValue : IPropertyKeyValue
{
public string Key { get; set; }
public object Value
{
get;
set;
}
}
public class PropertyKeyValue<TValue> : IPropertyKeyValue
{
object IPropertyKeyValue.Value { get { return this.Value; } }
public string Key { get; set; }
public TValue Value { get; set; }
}
public class IconProperty : PropertyKeyValue<ImageSource>
{
}
public class WindowManager : Caliburn.Micro.WindowManager
{
public List<IPropertyKeyValue> DefaultSettings { get { return _defaultSettings; } }
private readonly List<IPropertyKeyValue> _defaultSettings = new List<IPropertyKeyValue>();
private void Populate(ref IDictionary<string, object> settings)
{
if (DefaultSettings != null && DefaultSettings.Count > 0)
{
if (settings == null)
settings = new Dictionary<String, object>();
foreach (var prop in DefaultSettings)
{
settings[prop.Key] = prop.Value;
}
}
}
protected override System.Windows.Window CreateWindow(object rootModel, bool isDialog, object context, IDictionary<string, object> settings)
{
Populate(ref settings);
return base.CreateWindow(rootModel, isDialog, context, settings);
}
}
//bootstrapper
protected override object GetInstance(Type service, string key)
{
if (service == typeof(IWindowManager))
return this.Application.FindResource("wm");
return base.GetInstance(service, key);
}
/*
<local:WindowManager x:Key="wm">
<local:WindowManager.DefaultSettings>
<local:IconProperty Key="Icon" Value="favicon.ico"/>
</local:WindowManager.DefaultSettings>
</local:WindowManager>
*/
Heres an example of what I do. I just put it in the window definition.
<Window x:Class="YourApp.Views.ShellView"
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:BudgetPlannerMainWPF.Views"
mc:Ignorable="d" Icon="C:\...Path...\YourIcon.png"
Title="Your Title" Height="500" Width="910" FontSize="14"
WindowStyle="SingleBorderWindow" Topmost="True" SizeToContent="Width">
I have a WPF combobox that is bound to an enum like this:
<Window.Resources>
<local:EnumDescriptionConverter x:Key="enumDescriptionConverter"/>
<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="cityNamesDataProvider">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:MyModel+CityNamesEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<ComboBox x:Name="cityNameComboBox" ItemsSource="{Binding Source={StaticResource cityNamesDataProvider}}" SelectionChanged="cityNameComboBox_SelectionChanged">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource enumDescriptionConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The enum that I'm binding to has description attributes and looks like this:
public enum CityNamesEnum
{
[Description("New York City")]
NewYorkCity,
[Description("Chicago")]
Chicago,
[Description("Los Angeles")]
LosAngeles
}
I don't always want to display each enum value. Is it possible to toggle the visibility of one or more of the enum values? If these were ComboBoxItems, I think I could simply set the .Visibility property to hidden but since they're enum values, I'm not sure if this is possible. Does anyone know?
Why not just create a normal C# method which does the filtering for you and then have the ObjectDataProvider point to that method instead
static method IEnumerable<CityNamesEnum> MyFilter() {
yield return CityNames.NewYorkCity;
yield return CityNames.Chicago;
}
XAML
<ObjectDataProvider
MethodName="MyFilter"
ObjectType="{x:Type local:TheType}"
x:Key="cityNamesDataProvider">
</ObjectDataProvider>
The example here is applied to a ComboBox, but will work all the same for any Enum Binding. It will however do exactly what you want: Hide Enum values that don't have a Description. This also provides an easy Binding method. (as well as sorting them by Index though, can be easily changed in the function "SortEnumValuesByIndex()")
Origin:
This anwser is based on the original work of Brian Lagunas' EnumBindingSourceExtension + EnumDescriptionTypeConverter.
I have made modifications for it to better suit my needs.
What I changed:
Extended the Enum class with a boolean function that checks if the EnumValue has the [Description] attribute or not
public static bool HasDescriptionAttribute(this Enum value)
{
var attribute = value.GetType().GetField(value.ToString())
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.FirstOrDefault();
return (attribute != null);
}
Modified Brian's "ConvertTo()" function in "EnumDescriptionTypeConverter" to return "null" in case the [Description] attribute was not applied
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
{
...
// Original: return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString();
return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : null;
}
Modified Brian's "ProvideValue()" function in "EnumBindingSourceExtension" by editing it's return value by calling my own function
ProvideValue(IServiceProvider serviceProvider)
{
...
// Original: return enumValues
return SortEnumValuesByIndex(enumValues);
...
// Original: return tempArray
return SortEnumValuesByIndex(tempArray);
}
And adding my function to Sort the enum by Index (Original code had problems when going across Projects ..), and Strip out any Values that don't have the [Description] attribute:
private object SortEnumValuesByIndex(Array enumValues)
{
var values = enumValues.Cast<Enum>().ToList();
var indexed = new Dictionary<int, Enum>();
foreach (var value in values)
{
int index = (int)Convert.ChangeType(value, Enum.GetUnderlyingType(value.GetType()));
indexed.Add(index, value);
}
return indexed.OrderBy(x => x.Key).Select(x => x.Value).Where(x => x.HasDescriptionAttribute()).Cast<Enum>();
}
This example has been applied to ComboBoxes:
Note: Failures in Uploading images to the Server, so i added a URL to the images in question
<ComboBox x:Name="ConversionPreset_ComboBox" Grid.Row="4" Grid.Column="1" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:ConversionPreset}}" SelectedIndex="2" SelectionChanged="ConversionPreset_ComboBox_SelectionChanged" />
<ComboBox x:Name="OutputType_ComboBox" Grid.Row="4" Grid.Column="2" Margin="5,5,5,5" ItemsSource="{objects:EnumBindingSource EnumType={x:Type enums:Output}}" SelectedIndex="1" SelectionChanged="OutputType_ComboBox_SelectionChanged" />
With code behind:
private Enumeration.Output Output { get; set; }
private void OutputType_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> Output = (Enumeration.Output)OutputType_ComboBox.SelectedItem;
private Enumeration.ConversionPreset ConversionPreset { get; set; }
private void ConversionPreset_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
=> ConversionPreset = (Enumeration.ConversionPreset)ConversionPreset_ComboBox.SelectedItem;
The "ConversionPreset" Enum and a Picture of the ComboBox Rendered
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum ConversionPreset
{
[Description("Very Slow (Smaller File Size)")]
VerySlow = -2,
[Description("Slow (Smaller File Size)")]
Slow = -1,
[Description("Medium (Balanced File Size)")]
Medium = 0,
[Description("Fast (Bigger File Size)")]
Fast = 1,
[Description("Very Fast (Bigger File Size)")]
VeryFast = 2,
[Description("Ultra Fast (Biggest File Size)")]
UltraFast = 3
}
The "Output" Enum and a Picture of the ComboBox Rendered
[TypeConverter(typeof(EnumDescriptionTypeConverter))]
public enum Output
{
// This will be hidden in the Output
None = -1,
[Description("Video")]
Video = 0,
[Description("Audio")]
Audio = 1
}
Update
The code above is intended to Bind Enums and Manage their Output without the need for Code Behind to manually fill the ComboBoxes.
I just now read that you want to approach it from in Code, having more control over the ComboBox Content.
This should get you on your way just fine. Just create a class and paste this content into it:
using System;
using System.ComponentModel;
namespace Attributes
{
public class Hidden : Attribute { }
public class StartingDescription : DescriptionAttribute
{
public StartingDescription(string description) : base(description) { }
}
public class FinishedDescription : DescriptionAttribute
{
public FinishedDescription(string description) : base(description) { }
}
}
namespace Enumerations
{
using Attributes;
public enum Order
{
None = -1,
[Description("Get"),
StartingDescription("Getting"),
FinishedDescription("Got")]
Get = 0,
[Description("Initialize"),
StartingDescription("Initializing"),
FinishedDescription("Initialized")]
Initialize = 1,
[Description("Download"),
StartingDescription("Downloading"),
FinishedDescription("Downloaded")]
Download = 2
}
public enum Output
{
[Hidden]
None = -1,
[Description("Video")]
Video = 33,
[Description("Audio")]
Audio = 44
}
}
namespace Classes
{
using System.Linq;
public static class Extensions
{
public static string Format(this string value, params object?[] args)
=> String.Format(value, args);
public static bool HasAttribute(this Enum enumValue, Type attributeType)
{
var result = enumValue.GetType().GetField(enumValue.ToString())
.GetCustomAttributes(attributeType, false)
.FirstOrDefault();
return (result != null);
}
public static int GetIndex(this Enum enumValue)
=> (int)Convert.ChangeType(enumValue, Enum.GetUnderlyingType(enumValue.GetType()));
public static string GetDescription(this Enum enumValue, Type attributetype = null)
{
if (attributetype == null)
attributetype = typeof(DescriptionAttribute);
var field = enumValue.GetType().GetField(enumValue.ToString());
var attributes = Attribute.GetCustomAttributes(field, false).Where(x => x.GetType().Equals(attributetype)).Cast<DescriptionAttribute>();
var value = attributes.FirstOrDefault();
if (value != null)
return value.Description;
return enumValue.ToString();
}
}
}
namespace ExecutionNamespace
{
using System.Collections.Generic;
using System.Linq;
using Classes;
using Attributes;
using System.Diagnostics;
internal class Example
{
public static SortedList<int, string> GetEnumByIndex(Type enumType, Type outputType = null)
{
if (enumType == null)
throw new ArgumentNullException("enumType was not Definied");
var enumValues = Enum.GetValues(enumType).Cast<Enum>().ToList();
if ((enumValues == null) || (enumValues.Count == 0))
throw new ArgumentNullException("Could not find any Enumeration Values");
if (outputType == null)
outputType = typeof(DescriptionAttribute);
var indexed = new SortedList<int, string>();
foreach (var value in enumValues)
if (!value.HasAttribute(typeof(Hidden)))
indexed.Add(value.GetIndex(), value.GetDescription(outputType));
return indexed;
}
public static SortedList<string, string> GetEnumByValue(Type enumType, Type outputType = null)
{
if (enumType == null)
throw new ArgumentNullException("enumType was not Definied");
var enumValues = Enum.GetValues(enumType).Cast<Enum>().ToList();
if ((enumValues == null) || (enumValues.Count == 0))
throw new ArgumentNullException("Could not find any Enumeration Values");
if (outputType == null)
outputType = typeof(DescriptionAttribute);
var indexed = new SortedList<string, string>();
foreach (var value in enumValues)
if (!value.HasAttribute(typeof(Hidden)))
indexed.Add(value.ToString(), value.GetDescription(outputType));
return indexed;
}
public static void Run()
{
Type type = null;
type = typeof(Enumerations.Order);
Debug.WriteLine("{0} by Index".Format(type.ToString()));
foreach (var valuePair in GetEnumByIndex(type, typeof(StartingDescription)))
Debug.WriteLine("Index:{1} & Description:{2} ".Format(type.ToString(), valuePair.Key, valuePair.Value));
Debug.WriteLine("");
type = typeof(Enumerations.Order);
Debug.WriteLine("{0} by Value".Format(type.ToString()));
foreach (var valuePair in GetEnumByValue(type, typeof(StartingDescription)))
Debug.WriteLine("Value:{1} & Description:{2} ".Format(type.ToString(), valuePair.Key, valuePair.Value));
Debug.WriteLine("");
type = typeof(Enumerations.Output);
Debug.WriteLine("{0} by Index".Format(type.ToString()));
foreach (var valuePair in GetEnumByIndex(type))
Debug.WriteLine("Index:{1} & Description:{2} ".Format(type.ToString(), valuePair.Key, valuePair.Value));
Debug.WriteLine("");
type = typeof(Enumerations.Output);
Debug.WriteLine("{0} by Value".Format(type.ToString()));
foreach (var valuePair in GetEnumByValue(type))
Debug.WriteLine("Value:{1} & Description:{2} ".Format(type.ToString(), valuePair.Key, valuePair.Value));
Debug.WriteLine("");
}
}
}
Then just do this on like a Button Click, and watch the Output Window:
private void Button_Click(object sender, RoutedEventArgs e)
{
ExecutionNamespace.Example.Run();
}
It will bring you this output:
Enumerations.Order by Index
Index:-1 & Description:None
Index:0 & Description:Getting
Index:1 & Description:Initializing
Index:2 & Description:Downloading
Enumerations.Order by Value
Value:Download & Description:Downloading
Value:Get & Description:Getting
Value:Initialize & Description:Initializing
Value:None & Description:None
Enumerations.Output by Index
Index:33 & Description:Video
Index:44 & Description:Audio
Enumerations.Output by Value
Value:Audio & Description:Audio
Value:Video & Description:Video