If I write a WinForms visual control and want to make one of its properties of a value type nullable, do I need to create a special type converter or editor for it to make it fully usable in the VS Property Grid?
For example, .NET Framework provides the NullableConverter class. Do I need to attach it to my nullable property via the TypeConverterAttribute?
I tried to convert one of the int properties to int?, and it seems this property works fine in the Property Grid. I can specify a numeric value or clear this property to make it null again.
The property grid uses TypeDescriptor to gets its converter information. So for example, if you have this class:
public class MyClass
{
public int? NullableInteger { get; set; }
}
With this code:
// get first property's type converter
var cv = TypeDescriptor.GetProperties(typeof(MyClass))[0].Converter;
You will get the TypeConverter for the NullableInteger property, which is already NullableConverter.
So, you don't have to declare it manually.
Related
I want to understand how WPF converts the string value (Red) to the corresponding SolidColorBrush in the case below?
How can we do the same with our custom DependencyProperty?
<Button Background="Red" />
The conversion from string to Brush is performed by a BrushConverter instance, a TypeConverter which is registered like this:
[System.ComponentModel.TypeConverter(typeof(System.Windows.Media.BrushConverter))]
public abstract class Brush : ...
It will automatically be used for any property of type Brush.
Assuming you have a dependency property like
public static readonly DependencyProperty MyBrushProperty =
DependencyProperty.Register(
nameof(MyBrush),
typeof(Brush),
typeof(MyButton));
public Brush MyBrush
{
get { return (Brush)GetValue(MyBrushProperty); }
set { SetValue(MyBrushProperty, value); }
}
the following would work out of the box:
<local:MyButton MyBrush="Red" />
In case the question is about a custom property type - instead of a custom property of type Brush - see the other answer :-)
This is done using type converters, see TypeConverters and XAML.
This topic introduces the purpose of type conversion from string as a general XAML language feature. In the .NET Framework, the TypeConverter class serves a particular purpose as part of the implementation for a managed custom class that can be used as a property value in XAML attribute usage. If you write a custom class, and you want instances of your class to be usable as XAML settable attribute values, you might need to apply a TypeConverterAttribute to your class, write a custom TypeConverter class, or both.
A XAML processor needs two pieces of information in order to process an attribute value. The first piece of information is the value type of the property that is being set. Any string that defines an attribute value and that is processed in XAML must ultimately be converted or resolved to a value of that type. [...]
If the value is neither a parser-understood primitive nor an enumeration, then the type in question must be able to provide an instance of the type, or a value, based on a converted string. This is done by indicating a type converter class. The type converter is effectively a helper class for providing values of another class, both for the XAML scenario and also potentially for code calls in .NET code.
WPF has a few built-in type converters like in case of a Brush, the BrushConverter. You can see how the brush converter is implemented in .NET e.g. in the reference source or on GitHub for .NET Core.
As you can see from the documentation of Brush, it specifies an attribute for the converter.
[System.ComponentModel.TypeConverter(typeof(System.Windows.Media.BrushConverter))]
[System.Windows.Localizability(System.Windows.LocalizationCategory.None, Readability=System.Windows.Readability.Unreadable)]
public abstract class Brush : System.Windows.Media.Animation.Animatable, IFormattable
You do not create a type converter for a dependency property, but its underlying type, like Brush. The documenation shows in detail how to create type converters for custom types. In essence:
Create custom type converter that dervies from TypeConverter.
public sealed class MyCustomTypeConverter : TypeConverter
{
// ...conversion methods.
}
Implement the following methods.
CanConvertTo
CanConvertFrom
ConvertTo
ConvertFrom
Finally, apply the TypeConverterAttribute to your custom type.
In order for your custom type converter to be used as the acting type converter for a custom class by a XAML processor, you must apply the TypeConverterAttribute to your class definition. The ConverterTypeName that you specify through the attribute must be the type name of your custom type converter. With this attribute applied, when a XAML processor handles values where the property type uses your custom class type, it can input strings and return object instances.
[TypeConverter(typeof(MyCustomTypeConverter ))]
public class YourCustomType
{
// ...code of your custom type.
}
Following these steps, the XAML parser will automatically be able to convert strings to your type.
The BrushConverter, mentioned above, is a good example of a TypeConverter implementation to start from.
I have a viewModel with properties like the following and a set of specific attributes used throughout the viewmodels.
public class MyViewModel : BaseModel
{
[StringLength(50), Required]
[SetLockedForExistingEntities]
public string FirstName { get ... set ... }
public bool IsInNewMode { get; }
}
Now I want to apply such metaData in a view in a consistent way. Like... If bound, set TextBox maxlength from the MaxLengthAttribute. If SetLockedForExistingEntitiesAttribute is set, disable the control in case viewModel is not in some 'New' Mode etc..
Is that doable/a good idea to do with a custom MarkupExtension that replaces "Binding" for VM Bindings? Or would it be better to use a Behavior (applied via attached property) which tries to apply anything it can from the bound ViewModel property to the control it is attached to?
Usage would be like
(A) Attached dependencyproperty that reads the binding from TextBox.Text and applies behaviors
<TextBox Text="{Binding Model.FirstName, ValidatesOnDataErrors=True}" "bb:MyBindingHelper.ApplyViewModelBehaviors="True" />
(B) Custom MarkupExtension that does all in one
<TextBox Text="{BindingWithModelBasedBehaviors Model.FirstName}" />
You could write a markup extension that gets the property from the datacontext and reads attributes.
That would be kind of complicated but you can get the property name of properties where the source changed event was raised.
That looks rather like validation to me.
You could implement inotifydataerrorinfo in a base viewmodel and write code there that validates properties using attributes.
That's how the code in this works:
https://gallery.technet.microsoft.com/scriptcenter/WPF-Entity-Framework-MVVM-78cdc204
That works by the view telling the viewmodel which property's value just passed to the viewmodel.
You can extend the method you use for raising property changed to pass the property name to the validation.
Or you could even do the check from a method called in the property setter before you set the value on a property and not set the value if the new one fails validation.
As a specific property fails validation in a particular way you could run an action.
The production code version of that app I linked also has a dictionary of predicates used as well as attributes. They could have code in them references and sets other viewmodel properties.
I have a wpf application using Caliburn.Micro. I need to bind a ListBox to a collection of objects, but I want to display one of the object's fields, and also somehow to attach a Guid (another field) to each item. Could you please tell me how I can do that? I don't know if Caliburn.Micro has something specific for it, or I just have to use WPF.
Thanks.
(sorry for my bad english)
If the Guid field is part of your object, you do not need to store it on another place. The listbox will show a field but it is still bounded to the original object, you can get it with ((MyObjectType)MyListBox.SelectedItem).Guid. With Caliburn it is even easier since you just need to bind a property on your VM to SelectedItem.
But if the Guid is not part of your object, you can use the Tag property, as Paul Sasik said. I do not like to use the Tag property so this is another easy (and more flexbible) way you can solve this, you need to encapsulate your object on another object:
public class GuidObject<T>
{
public T Instance {get;set;}
public Guid Guid {get;set;}
}
You can use it like this:
//this is your original guidless items list
var myObjectsList = new[] { new MyObject { Name = "Dostoyevsky" },
new MyObject { Name = "Ozzy" } };
var myObjectsWithGuidList = new ObservableCollection<GuidObject<MyObject>>();
//encapsulate each MyObject on a GuidObject and include a Guid
//if your myObjectsList is already a List, you do not need to call ToList()
myObjectsList.ToList().ForEach(o => myObjectsWithGuidList.Add(new GuidObject<MyObject>() { Instance = o, Guid = Guid.NewGuid() }));
//now myObjectsWithGuidList contains a list of your itens and a Guid field, you can bind it to your ListBox
Here you can see this running.
You can use the Tag property of each ListBox object to store arbitrary information.
From the link:
This property is analogous to Tag properties in other Microsoft
programming models, such as Microsoft Visual Basic for Applications
(VBA) or Windows Forms. Tag is intended to provide a pre-existing
property location where you can store some basic custom information
about any FrameworkElement without requiring you to subclass an
element.
Because this property takes an object, you would need to use the
property element usage in order to set the Tag property in Extensible
Application Markup Language (XAML) to anything other than an object
with a known and built-in type converter, such as a string. Objects
used in this manner are typically not within the standard Windows
Presentation Foundation (WPF) namespaces and therefore may require
namespace mapping to the external namespace in order to be introduced
as XAML elements.
I'm creating a UserControl which will be used in various scenarios. I need to expose a collection of strings from the UserControl and I'm not sure how to do it.
The two possible uses I see are:
a control on its containing control binds to the collection, e.g. a ListBox;
a property on the containing control's ViewModel needs to bind to the collection.
I can get the former to work with a public ObservableCollection<String> property on the UserControl but it won't work for the latter. (I get "Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.Collections.ObjectModel.ObservableCollection`1[System.String]'.")
Is there a solution that will work for both?
EDIT
This is my stab at a UML diagram showing what I'm doing:
I think the problem is just that the binder can't understand generics. You should be able to get around that by inheriting from ObservableCollection<string> to make a non-generic class. You can use something like this:
class StringCollection : ObservableCollection<string> { }
Since you are using that property as a target of a binding, you must declare it as a DependencyProperty:
class ObjectSelectorView
{
public StringCollection ObjectNames
{
get { return (StringCollection)GetValue(ObjectNamesProperty); }
set { SetValue(ObjectNamesProperty, value); }
}
// Using a DependencyProperty as the backing store for ObjectNames.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty ObjectNamesProperty =
DependencyProperty.Register("ObjectNames", typeof(StringCollection),
typeof(ObjectSelectorView), null);
....
}
I would suggest that the error you are seeing is actually the result of your not implementing this property as dependency property. You should be using a dependency property here.
Don't expose the property on your control as a concrete ObservableCollection of anything. Instead expose the property as a simple non-generic IList.
In your control's constructor assign an initial empty instance of ObservableCollection<String> to this property. However the property should have a public setter and therefore your initial collection instance may be replaced by some other implementer of IList. Therefore you should limit your usage of this property internally to IList or gracefully degrade behaviour if the current instance does not have the other interfaces you might want.
Is it possible to introduce 'custom' attributes into different UI Elements in XAML ? Also to read them later like we add attributes for server controls in ASP.NET ?
I intend to read specific attributes and operate on them together.
It sounds like you're trying to find Attached Properties.
An attached property lets you add in a property, definable in Xaml, which can be "attached" to any UIelement. You then retrieve them in code like any other Dependency Property.
Here is the approach I tend to take with this.
Create a new class file called Meta:-
namespace SilverlightApplication1
{
public static class Meta
{
#region SomeValue
public static string GetSomeValue(DependencyObject obj)
{
return (string)obj.GetValue(SomeValueProperty);
}
public static void SetSomeValue(DependencyObject obj, string value)
{
obj.SetValue(SomeValueProperty, value);
}
public static readonly DependencyProperty SomeValueProperty =
DependencyProperty.RegisterAttached("SomeValue", typeof(string), typeof(Meta),
new PropertyMetadata(null));
#end region
#region SomeOtherValue
// Boilerplate code from above.
#end region
}
}
A value can now be attached in XAML like this:-
<TextBox x:Name="txt" local:Meta.SomeValue="Hello, World!" />
At some point in code this value can be retrieved with:-
string value = Meta.GetSomeValue(txt);
Note you don't have to stick with String as the type of the property you can pretty much use any type you like with the limitation that if you can to attach it in XAML the type must be compatible with the way XAML constructs objects (for example requires a default constructor).
The way I've accomplished that is by creating a new class that inherits the base control.
For example, I have a class called WebTextBox that inherits TextBox. And inside WebTextBox are some custom properties and events. By doing this you're inheriting all the behaviors of the TextBox control. But you can get creative here if you choose, even modifying the behavior by overriding events and such.
Anyway, after you create the class you'll then have to add the namespace for the project to the XAML. Something like this:
xmlns:me="clr-namespace:YourNamespace;assembly=YourAssembly"
And then you can add a WebTextBox (or whatever you call it) like this:
<me:WebTextBox CustomAttribute="cool stuff" />