I have a PropertyGrid on my form. My boss thinks it's ugly. Uncouth. Unsophisticated.
He wants a nice, neat, clean form. Here's the catch: One of the properties is a collection of our home-grown objects. He likes the collection editor for this collection.
I know I can build my own collection editor. But is there a clean, simple solution to save me a few hours of coding, such that I can create and use a Collection editor directly without using the property grid?
You can get this functionality from the UITypeEditor (via TypeDescriptor), but it isn't trivial - you need to set up an IServiceProvider, an IWindowsFormsEditorService, and ideally an ITypeDescriptorContext - quite a bit of faff. It might be simpler to do it by hand if you aren't familiar with those tools.
Alternatively - take a look at SmartPropertyGrid.NET, an alternative to PropertyGrid.
Update: here's a working example... definitely non-trivial, but feel free to steal the code. It only works for modal editors, not drop-down. It also isn't a great example of "separation of concerns". The MyHelper class is the interesting one.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
class Foo
{
public Foo() { Bars = new List<Bar>(); }
public List<Bar> Bars { get; private set; }
}
class Bar
{
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
}
static class Program
{
[STAThread]
static void Main()
{
Foo foo = new Foo();
Bar bar = new Bar();
bar.Name = "Fred";
bar.DateOfBirth = DateTime.Today;
foo.Bars.Add(bar);
Application.EnableVisualStyles();
using(Form form = new Form())
using (Button btn = new Button())
{
form.Controls.Add(btn);
btn.Text = "Edit";
btn.Click += delegate
{
MyHelper.EditValue(form, foo, "Bars");
};
Application.Run(form);
}
}
}
class MyHelper : IWindowsFormsEditorService, IServiceProvider, ITypeDescriptorContext
{
public static void EditValue(IWin32Window owner, object component, string propertyName) {
PropertyDescriptor prop = TypeDescriptor.GetProperties(component)[propertyName];
if(prop == null) throw new ArgumentException("propertyName");
UITypeEditor editor = (UITypeEditor) prop.GetEditor(typeof(UITypeEditor));
MyHelper ctx = new MyHelper(owner, component, prop);
if(editor != null && editor.GetEditStyle(ctx) == UITypeEditorEditStyle.Modal)
{
object value = prop.GetValue(component);
value = editor.EditValue(ctx, ctx, value);
if (!prop.IsReadOnly)
{
prop.SetValue(component, value);
}
}
}
private readonly IWin32Window owner;
private readonly object component;
private readonly PropertyDescriptor property;
private MyHelper(IWin32Window owner, object component, PropertyDescriptor property)
{
this.owner = owner;
this.component = component;
this.property = property;
}
#region IWindowsFormsEditorService Members
public void CloseDropDown()
{
throw new NotImplementedException();
}
public void DropDownControl(System.Windows.Forms.Control control)
{
throw new NotImplementedException();
}
public System.Windows.Forms.DialogResult ShowDialog(System.Windows.Forms.Form dialog)
{
return dialog.ShowDialog(owner);
}
#endregion
#region IServiceProvider Members
public object GetService(Type serviceType)
{
return serviceType == typeof(IWindowsFormsEditorService) ? this : null;
}
#endregion
#region ITypeDescriptorContext Members
IContainer ITypeDescriptorContext.Container
{
get { return null; }
}
object ITypeDescriptorContext.Instance
{
get { return component; }
}
void ITypeDescriptorContext.OnComponentChanged()
{}
bool ITypeDescriptorContext.OnComponentChanging()
{
return true;
}
PropertyDescriptor ITypeDescriptorContext.PropertyDescriptor
{
get { return property; }
}
#endregion
}
Related
This is my UnityResolver Class to create the instance of IUnityContainer
public sealed class UnityResolver
{
private static IUnityContainer _unityContainer;
private static volatile UnityResolver _unityresolverinstance;
private static object syncRoot = new Object();
public static IUnityContainer UnityContainerInitiation
{
get
{
if (_unityContainer == null)
{
if (_unityresolverinstance == null)
{
lock (syncRoot)
{
if (_unityresolverinstance == null)
_unityresolverinstance = new UnityResolver();
}
}
}
return UnityResolver._unityContainer;
}
}
public UnityResolver()
{
_unityContainer = new UnityContainer();
_unityContainer.RegisterType<MaintainRouteViewModel>();
}
}
Below is my Base View and Its ViewModelCode
public partial class MaintainRouteView : UserControl
{
public MaintainRouteViewModel maintainRouteViewModel = null;
IUnityContainer container;
public MaintainRouteView()
{
InitializeComponent();
container = UnityResolver.UnityContainerInitiation;
maintainRouteViewModel = container.Resolve<MaintainRouteViewModel>();
this.DataContext = maintainRouteViewModel;
}
///This button will navigate to the child view.
private void AddRoute_Click(object sender, RoutedEventArgs e)
{
pageAnimationControl.ShowPage(new AddNewRouteView());
}
}
Its ViewModel..
public class MaintainRouteViewModel : viewModelbase
{
private string _statusSuccessMessage = null;
private string _statusFailMessage =null;
private ObservableCollection<RouteDetailsModel> _routeDetailsCollection;
public ObservableCollection<RouteDetailsModel> routeDetailsCollection
{
get
{
return this._routeDetailsCollection;
}
set
{
this._routeDetailsCollection = value;
RaisePropertyChanged("routeDetailsCollection");
}
}
public string StatusSuccessMessage
{
get
{
return _statusSuccessMessage;
}
set
{
_statusSuccessMessage = value;
this.RaisePropertyChanged("StatusSuccessMessage");
}
}
public string StatusFailMessage
{
get { return _statusFailMessage; }
set
{
_statusFailMessage = value;
this.RaisePropertyChanged("StatusFailMessage");
}
}
public MaintainRouteViewModel()
{
///it will load some data to the Observablecollection
getAllCurrentRouteData();
}
}
Now Below is my Child View and its ViewModel....
public partial class AddNewRouteView : UserControl
{
public AddNewRouteView()
{
InitializeComponent();
IUnityContainer container = UnityResolver.UnityContainerInitiation;
this.DataContext = container.Resolve<AddNewRouteViewModel>();
}
}
Its ViewModel....
public class AddNewRouteViewModel : viewModelbase
{
private MaintainRouteViewModel maintainRouteViewModel;
public ICommand SaveCommand
{
get;
set;
}
[InjectionConstructor]
public AddNewRouteViewModel(MaintainRouteViewModel maintainRouteViewModel)
{
this.maintainRouteViewModel = maintainRouteViewModel;
SaveCommand = new DelegateCommand<object>((a) => ValidateNewRoute());
}
private void ValidateNewRoute()
{
bool flag = saveAndValidate();
if(flag)
{
updateRouteStatus();
}
}
public void updateRouteStatus()
{
maintainRouteViewModel.StatusSuccessMessage = "New Route successfully Added..";
}
}
}
Can Anyone Tell me how to use this way to get the same object of MaintainRouteViewModel in my Child VM Constructor So that i will show the Updated Status Message in my Base view MaintainRouteView???
*It will Work Fine If i replace my MaintainRouteView with below code :
this Is an another approach to use IOC .i previously using this in my project. it Works Fine for me but now i want to implement the same thing using Unity Container. Please Help.
public partial class MaintainRouteView : UserControl
{
public MaintainRouteViewModel maintainRouteViewModel = null;
public MaintainRouteView()
{
InitializeComponent();
maintainRouteViewModel = new MaintainRouteViewModel();
this.DataContext = maintainRouteViewModel;
}
private void AddRoute_Click(object sender, RoutedEventArgs e)
{
pageTransitionControl.ShowPage(
new AddNewRouteView
{
DataContext = new AddNewRouteViewModel(maintainRouteViewModel)
});
}
}
I am able to solve this issue using the LifeTime Management of Unity Container Register Types.
it will work fine if i tell the container to create a singleton instance of the MaintainRouteViewModel Class.
using :
container.RegisterType<MaintainRouteViewModel>(
new ContainerControlledLifetimeManager());
But it's just a workaround to get the expected result. i want to achieve it using a proper dependency injection without any singleton instance principle. Can anyone please help to provide the solution.
Please consider my codes below:
I'm getting an error Constructor on type 'System.String' not found. when I add new string to the collection using the PropertyGrid control.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
propertyGrid1.SelectedObject = Class1.Instance.StringCollection;
}
}
-----------------------------------------------------------------------------
public sealed class Class1
{
private static Class1 _instance = new Class1();
private List<string> _stringListCollection = new List<string>();
public Class1()
{
}
public static Class1 Instance
{
get { return _instance; }
}
public List<string> StringCollection
{
get { return _stringListCollection; }
set { _stringListCollection = value; }
}
}
When you assign List of something to PropertyGrid, it tries to show single row with modify ... button,
where default modify dialog require Item class to have default constructor, which is not right in case of string
You can create class with default constructor and string property in it, and assign a collection of that class instead of string
Or you can use EditorAttribute to override default editor
Hope this helps
Here is a little class that implements CollectionEditor and fixes the problem for a list of strings:
public class CollectionEditorBase : CollectionEditor
{
public CollectionEditorBase(Type type) : base(type) { }
protected override object CreateInstance(Type itemType)
{
//Fixes the "Constructor on type 'System.String' not found." when it is an empty list of strings
if (itemType == typeof(string)) return string.Empty;
else return Activator.CreateInstance(itemType);
}
}
Now just change the editor to be used with you list of strings:
public class MySettings
{
[Editor(typeof(CollectionEditorBase), typeof(System.Drawing.Design.UITypeEditor))]
public List<string> ListOfStrings { get; set; } = new List<string>();
}
And then you us an instance of MySettings in the property grid:
propertyGrid1.SelectedObject = new MySettings();
At the top of your class, you would have to use System.ComponentModel and System.ComponentModel.Design or fully qualifies these names in your code.
I have a server control, inheriting from PlaceHolder.
Basically, all it is, is a placeholder, with the top part having a "<div class etc...", and the bottom closing it off.
So, typical usage would be
<control:control runat="server" id="phControl">
<asp:TextBox runat="server" id="txtControl">
<asp:DropDownList runat="server"id="ddlControl">
</control:control>
or something similar.
It has struck me that if I postback to the control, it loses all the items in the ddlControl (or whatever), and that implementing IPostBackHandler apparently would solve all my woes.
I had a quick glance through the documentation, but am still not really sure what I am implementing (obviously I have the method names, but I don't really get what is expected in here)
Any pointers in the right direction would be greatly appreciated.
Thanks,
Tim
Its looks like you just want a server control that can contains other controls or a "template", I have just done this using the example at: http://msdn.microsoft.com/en-us/library/ms178657.aspx
This should handle all the work done on postback.
A basic example adapted from the above link:
using System;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.WebControls;
namespace Made4Print.Web.UI
{
[AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal), Designer(typeof(VacationHomeDesigner)), DefaultProperty("Title"), ToolboxData("<{0}:TemplateContainer runat=\"server\"> "),]
public class TemplateContainer : CompositeControl
{
private ITemplate templateValue;
private TemplateOwner ownerValue;
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public TemplateOwner Owner
{
get
{
return ownerValue;
}
}
[Browsable(false), PersistenceMode(PersistenceMode.InnerProperty), DefaultValue(typeof(ITemplate), ""), Description("Control template"), TemplateContainer(typeof(TemplateContainer))]
public virtual ITemplate Template
{
get
{
return templateValue;
}
set
{
templateValue = value;
}
}
protected override void CreateChildControls()
{
Controls.Clear();
ownerValue = new TemplateOwner();
ITemplate temp = templateValue;
if (temp == null)
{
temp = new DefaultTemplate();
}
temp.InstantiateIn(ownerValue);
this.Controls.Add(ownerValue);
}
public override void DataBind()
{
CreateChildControls();
ChildControlsCreated = true;
base.DataBind();
}
}
[ToolboxItem(false)]
public class TemplateOwner : WebControl
{
}
#region DefaultTemplate
sealed class DefaultTemplate : ITemplate
{
void ITemplate.InstantiateIn(Control owner)
{
// Create Controls Here
//Label title = new Label();
//title.DataBinding += new EventHandler(title_DataBinding);
//owner.Controls.Add(title);
}
//void title_DataBinding(object sender, EventArgs e)
//{
// Label source = (Label)sender;
// TemplateContainer container = (TemplateContainer)(source.NamingContainer);
// source.Text = container.Title;
//}
}
#endregion
public class VacationHomeDesigner : ControlDesigner
{
public override void Initialize(IComponent Component)
{
base.Initialize(Component);
SetViewFlags(ViewFlags.TemplateEditing, true);
}
public override string GetDesignTimeHtml()
{
return "<span>[Template Container Control]</span>";
}
public override TemplateGroupCollection TemplateGroups
{
get
{
TemplateGroupCollection collection = new TemplateGroupCollection();
TemplateGroup group;
TemplateDefinition template;
TemplateContainer control;
control = (TemplateContainer)Component;
group = new TemplateGroup("Item");
template = new TemplateDefinition(this, "Template", control, "Template", true);
group.AddTemplateDefinition(template);
collection.Add(group);
return collection;
}
}
}
}
I have a List<> (my custom class). I want to display a specific item in this list in a box on the PropertyGrid control. At the end of the box I would like the [...] button. When clicked, it would open up a form which, among other things, would allow them to pick one of the items from the List. When closed, the PropertyGrid would be updated to reflect the selected value.
Any help appreciated.
You need to implement a modal UITypeEditor, using the IWindowsFormsEditorService service to display it:
using System.ComponentModel;
using System.Drawing.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System;
class MyType
{
private Foo foo = new Foo();
public Foo Foo { get { return foo; } }
}
[Editor(typeof(FooEditor), typeof(UITypeEditor))]
[TypeConverter(typeof(ExpandableObjectConverter))]
class Foo
{
private string bar;
public string Bar
{
get { return bar; }
set { bar = value; }
}
}
class FooEditor : UITypeEditor
{
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override object EditValue(ITypeDescriptorContext context, System.IServiceProvider provider, object value)
{
IWindowsFormsEditorService svc = provider.GetService(typeof(IWindowsFormsEditorService)) as IWindowsFormsEditorService;
Foo foo = value as Foo;
if (svc != null && foo != null)
{
using (FooForm form = new FooForm())
{
form.Value = foo.Bar;
if (svc.ShowDialog(form) == DialogResult.OK)
{
foo.Bar = form.Value; // update object
}
}
}
return value; // can also replace the wrapper object here
}
}
class FooForm : Form
{
private TextBox textbox;
private Button okButton;
public FooForm() {
textbox = new TextBox();
Controls.Add(textbox);
okButton = new Button();
okButton.Text = "OK";
okButton.Dock = DockStyle.Bottom;
okButton.DialogResult = DialogResult.OK;
Controls.Add(okButton);
}
public string Value
{
get { return textbox.Text; }
set { textbox.Text = value; }
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Form form = new Form();
PropertyGrid grid = new PropertyGrid();
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = new MyType();
Application.Run(form);
}
}
Note: if you need to access something about the context of the property (the parent object etc), that is what the ITypeDescriptorContext (in EditValue) provides; it tells you the PropertyDescriptor and Instance (the MyType) that is involved.
I have written a control in C# that derives from System.Windows.Forms.Control. I have added a property Selected to which I want to databind to a business entity using a BindingSource.
I’ve implemented the PropertyNameChanged pattern by adding a SelectedChanged event that I fire when the Selected property is changed.
This is my code:
public partial class RateControl : Control
{
[Category("Property Changed")]
public event EventHandler SelectedChanged;
public int Selected
{
get
{ return m_selected; }
set
{
if (m_selected != value)
{
m_selected = value;
OnSelectedChanged();
Invalidate();
}
}
}
protected virtual void OnSelectedChanged()
{
if (this.SelectedChanged != null)
this.SelectedChanged(this, new EventArgs());
}
}
When I bind to the Selected property, I see the event being subscibed to. The event is also fired when the property changes.
However the business entity is not updated. I don’t even see the getter of the Selected property being accessed.
What am I missing?
Have you got the binding's update mode set to DataSourceUpdateMode.OnPropertyChanged? Either via binding.DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;, or using one of the DataBindings.Add(...) overloads.
The following works for me to push values to the business object...
using System;
using System.Diagnostics;
using System.Windows.Forms;
class MyForm : Form
{
public MyForm()
{
MyBusinessObject obj = new MyBusinessObject();
Button btn = new Button();
btn.Click += delegate { Foo++; };
DataBindings.Add("Foo", obj, "Bar", false, DataSourceUpdateMode.OnPropertyChanged);
DataBindings.Add("Text", obj, "Bar");
Controls.Add(btn);
}
private int foo;
public event EventHandler FooChanged;
public int Foo
{
get { return foo; }
set
{
if (foo != value)
{
foo = value;
Debug.WriteLine("Foo changed to " + foo);
if (FooChanged != null) FooChanged(this, EventArgs.Empty);
}
}
}
}
class MyBusinessObject
{
private int bar;
public event EventHandler BarChanged;
public int Bar
{
get { return bar; }
set
{
if (bar != value)
{
bar = value;
Debug.WriteLine("Bar changed to " + bar);
if (BarChanged != null) BarChanged(this, EventArgs.Empty);
}
}
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.Run(new MyForm());
}
}