As we know that by default the winform PropertyGrid is able to edit properties of a predefined class. However, some times we might need to edit dynamic created objects. Refer to the code below:
ParamForm.Show(new { Firstname = "John", Lastname = "Herby" })
The ParamForm window contains 2 controls, a PropertyGrid and a Button. It is designed to be able to edit dynamic objects which contains string or boolean fields only.
public static dynamic Show(dynamic args)
{
var frm = new ParamForm(args);
frm.ShowDialog();
return frm.Result;
}
public ParamForm(dynamic args)
{
InitializeComponent();
propertyGrid.SelectedObject = ag;
}
The problem is that the Firstname & Lastname displayed in PropertyGrid control is grayed out and cannot be edited. So how to make the PropertyGrid able to edit dynamic created objects?
Anonymous types have read only property descriptors (used by the property grid) by design (see here for more on this: Non-read only alternative to anonymous types).
You can however use tricks such as the DynamicTypeDescriptorWrapper class demonstrated here: Fun with C# 4.0’s dynamic that implement the ICustomTypeDescriptor Interface
Related
Can a WPF user control be written in F#?
Lets say I have a standard WPF/C# user control as:
public class DataGridAnnotationControl : UserControl
{
static DataGridAnnotationControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(DataGridAnnotationControl), new FrameworkPropertyMetadata(typeof(DataGridAnnotationControl)));
}
public DataGridAnnotationControl()
{
BorderBrush = Brushes.Black;
Background = Brushes.AliceBlue;
BorderThickness = new Thickness(20, 20, 20, 20);
}
public string LastName
{
get { return (string)GetValue(LastNameProperty); }
set { SetValue(LastNameProperty, value); }
}
public static readonly DependencyProperty LastNameProperty =
DependencyProperty.Register("LastName", typeof(string), typeof(DataGridAnnotationControl), new PropertyMetadata(string.Empty));
}
How is this coded in F#?
TIA
In general, creating the user control in F# (without a library) will typically be quite different than in C#.
The main issue is you can't use partial classes, so the designer will not function. Even if you ditch the designer, the typical workflow with XAML files does not work properly. To do this in "pure" F#, you typically need to write the UI in code vs doing it in XAML and allowing the generated InitializeComponent() method to wire things together.
However, one way to get there in a more "natural" method is to use FsXaml. It allows you to write user controls directly which become usable in a similar way to C# developed ones. This is done via a type provider and overriding the default information.
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 may be missing something about the fundamentals of WPF design, but I was wondering why many properties on WPF controls are exposed as the type 'Object'?
For example, MenuItem.Icon is an Object, and so is MenuItem.ToolTip. As a near first time user, this was very confusing to me (it felt like I was using a dynamic programming language, having no idea whether setting ToolTip to a String type would even work or not). Moreover, I tried to set the Icon to a 'System.Drawing.Icon' and I get an ArgumentException of "Argument 'picture' must be a picture that can be used as a Icon." Shouldn't the property be typed so it can at least describe what in the world you're supposed to give it?
Honestly, my guess as to the reason is because you cannot implement an interface on a type you did not create (without creating a wrapper), but that's just a guess.
Thanks very much for your answers and insights!
The main reason in my opinion is that since an Object is the "ultimate base class of all classes in the .Net Framework". This gives you flexibility, in WPF you are not limited to a predefined type. Wpf is different and has a learning curve, but it does give you a lot more options to create a product that looks good.
i.e.
You can assign a TextBox to a ToolTip:
TextBox tb = new TextBox();
tb.Text = "Hello World";
this.ToolTip = tb;
a Bitmap
BitmapImage myBitmapImage = new BitmapImage(new Uri((#"C:\Temp\20100706.jpg")));
Image image = new Image();
image.Source = myBitmapImage;
this.ToolTip = image;
and assigning a Image to a MenuItem
BitmapImage myBitmapImage = new BitmapImage(new Uri((#"C:\Temp\20100706.jpg")));
Image image = new Image();
image.Source = myBitmapImage;
menuItem1.Icon = image;
Consider the ToolTip for example. A ToolTip is a ContentControl, which can contain any type of CLR (Common Language Runtime) object (such as a string or a DateTime object) or a UIElement object (such as a Rectangle or a Panel). This enables you to add rich content to controls such as Button and CheckBox.
For this reason, elements such as ToolTip are exposed as Object, that is the root of the type hierarchy (with resulting ease of use, flexibility and clarity of the code).
Imagine these properties were typed as UIElements (or some other WPF specific object). How would you add objects to your controls that were not UIElements?
You would have to provide a wrapper derived from a WPF object that exposes the information you require. Most of the time the wrapper would simply call ToString() of the object being wrapped. Seeing as most types you will be using provide a good enough default implementation of ToString() it makes sense to just call this instead of making the developer write wrappers for everything.
Second, imagine if they were typed as some interface. What if you want to communicate something that this interface can't? The only options are (a) the developer lives with the limitations of the framework or (b) Microsoft updates the interface and breaks all existing code which has already been written.
Also consider if you are using a pattern like MVVM. The current design means your view models can expose properties that are not tied to WPF in any way which ultimately makes your code more reusable across different technologies.
Finally, remember that there is a difference between the object that represents the property and they way that WPF renders that information. E.G. if you use a primitive type such as System.String, WPF will create a textblock and set the text property to the result of ToString(). This allows a very clean separation between the data that is displayed by the UI and they way the information is rendered by the UI.
Take a simple class that represents a menu item, for example:
public class MenuItem
{
public string Text { get; set; }
public bool IsChecked { get; set; }
public bool IsEnabled { get; set; }
}
This type only exposes data about the menu item and has no information about how this information should be rendered. In fact, apart from the name of the class (MenuItem) this is not even specific to a menu item and the same data could be used in another UI control such as a checked listbox with no changes required. If the class exposed WPF specific user interface elements then the information would need to be adapted by another type for each different user interface control.
My understanding is that Silverlight does not support DataTemplates with a DataType attribute.
How then would you accomplish the following in SL (author is Josh Smith, full link below). In a nutshell, he's saying that if you bind a TabControl's tab pages to a collection of ViewModels, WPF will figure out how to display each one on the fly by looking for a DataTemplate that has the appropriate (matching) DataType set. Way cool, but I'm wondering how you would (could?) do this in Silverlight.
Applying a View to a ViewModel
MainWindowViewModel indirectly adds
and removes WorkspaceViewModel
objects to and from the main window's
TabControl. By relying on data
binding, the Content property of a
TabItem receives a
ViewModelBase-derived object to
display. ViewModelBase is not a UI
element, so it has no inherent support
for rendering itself. By default, in
WPF a non-visual object is rendered by
displaying the results of a call to
its ToString method in a TextBlock.
That clearly is not what you need,
unless your users have a burning
desire to see the type name of our
ViewModel classes!
You can easily tell WPF how to render
a ViewModel object by using typed
DataTemplates. A typed DataTemplate
does not have an x:Key value assigned
to it, but it does have its DataType
property set to an instance of the
Type class. If WPF tries to render one
of your ViewModel objects, it will
check to see if the resource system
has a typed DataTemplate in scope
whose DataType is the same as (or a
base class of) the type of your
ViewModel object. If it finds one, it
uses that template to render the
ViewModel object referenced by the tab
item's Content property.
The MainWindowResources.xaml file has
a ResourceDictionary. That dictionary
is added to the main window's resource
hierarchy, which means that the
resources it contains are in the
window's resource scope. When a tab
item's content is set to a ViewModel
object, a typed DataTemplate from this
dictionary supplies a view (that is, a
user control) to render it, as shown
in Figure 10.in Figure 10.
http://msdn.microsoft.com/en-us/magazine/dd419663.aspx in Figure 10.
Here is ONE way you can do it. I have used a technique like this in the past, and had great success with it.
Consider a very simple container that will create the view for you like this:
public class ViewMapper : ContentControl
{
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property.Name == "DataContext")
WhenDataContextChanges();
}
private void WhenDataContextChanges()
{
if (DataContext == null)
Content = null;
else
Content = ViewFactory.GetView(DataContext.GetType());
}
}
EDIT
So, you can use this control to do the mapping for you:
<Border DataContext="{Binding MyViewModel}">
<ViewMapper />
</Border>
END EDIT
Note that ViewMapper simply waits for the data context to change, looks up the appropriate view for the data type, and creates a new one. It relies on ViewFactory, which is a very simple static lookup that maps types to views:
public class ViewFactory
{
private static readonly Dictionary<string, Func<UIElement>> _registry = new Dictionary<string, Func<UIElement>>();
private static string Key(Type viewModelType)
{
return viewModelType.FullName;
}
public static void RegisterView(Type viewModelType, Func<UIElement> createView)
{
_registry.Add(Key(viewModelType), createView);
}
public static UIElement GetView(Type viewModelType)
{
var key = Key(viewModelType);
if (!_registry.ContainsKey(key))
return null;
return _registry[key]();
}
}
Then, you simply need to register the view mappings some place:
ViewFactory.RegisterView(typeof(SomeViewModel), () => new SomeView());
Note that ViewFactory could just as easily use Activator.CreateInstance instead of using the Func mechanism. Take that one step further, and you can use an IoC container... You could always decide to map via a string Name property on the ViewModel instead of a type... the possibilities are endless and powerful here.
I am using the DataForm for an entity with about 40 attributes. I'm happy with how the form displays all but 3 of the attributes. These 3 attributes happen to be lists of items.
I don't want to have to code out an entire edit template, seems very counter productive.
<dataFormToolkit:DataForm AutoGenerateFields="True" CurrentItem="{Binding XXX, Mode=TwoWay, Source={StaticResource XXXViewModel}}" >
<dataFormToolkit:DataField Label="Client" >
<ListBox ItemsSource="{Binding Client}"></ListBox>
</dataFormToolkit:DataField>
</dataFormToolkit:DataForm>
The the WCF RIA Services includes a Silverlight Business Application project template that demonstrates creating a CustomDataForm where they override OnAutoGeneratingField and modify the field for just the attributes you want. I've copied the code here for you to illustrate the idea but I'd suggest you check out the real thing to see how they are using the ReplaceTextBox extension method to deal with the Data Binding as well. Download link.
public class CustomDataForm : DataForm
{
protected override void OnAutoGeneratingField(DataFormAutoGeneratingFieldEventArgs e)
{
// Get metadata about the property being defined
PropertyInfo propertyInfo = this.CurrentItem.GetType().GetProperty(e.PropertyName);
// Do the password field replacement if that is the case
if (e.Field.Content is TextBox && this.IsPasswordProperty(propertyInfo))
{
e.Field.ReplaceTextBox(new PasswordBox(), PasswordBox.PasswordProperty);
}
// Keep this newly generated field accessible through the Fields property
this.fields[e.PropertyName] = e.Field;
// Call base implementation (which will call other event listeners)
base.OnAutoGeneratingField(e);
}
}
It will work : try that
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false)]
public class IsPassword : System.Attribute { }
public class CustomDataForm : DataForm
{
protected override void OnAutoGeneratingField(DataFormAutoGeneratingFieldEventArgs e)
{
// Get metadata about the property being defined
PropertyInfo propertyInfo = this.CurrentItem.GetType().GetProperty(e.PropertyName);
// Do the password field replacement if that is the case
var attributes = propertyInfo.GetCustomAttributes(typeof(IsPassword), false).ToList();
if (attributes.Any(obj=>obj is IsPassword))
{
PasswordBox box= new PasswordBox();
Binding binding = new Binding(e.PropertyName);
binding.Mode = BindingMode.TwoWay;
box.SetBinding(PasswordBox.PasswordProperty, binding);
e.Field.Content=box;
}
base.OnAutoGeneratingField(e);
}
}
then just add [IsPassword] to your property
I'm pretty sure it's not possible. If I were you I would swallow my grief and create that edit template.
The only alternative I can see is to work with the data in your viewmodel and create a separate class that holds the 37 properties that need no changing. Then you make a separate entity for the 3 that need special attention. This way you could have two data forms, one autogenerated and one custom. Hopefully you can then work with styling them so they look like one form. A lot of work, I know, but it might be even more work to create the full edit template.