I'm using a PropertyGrid in an application I am writing to allow users to view and sometimes edit instances of my objects. Sometimes the user may have a file open in read/write mode where they can make changes to the file through the property grid. In other cases they may have a file open in read only mode, and should not be able to make any changes to the objects through the PropetyGrid. My classes also have dynamic properties which are returned by implementing ICustomTypeDescriptor. Which is why I really want to take advantage of the built in flexibility of a PropertyGrid control.
There doesn't seem to be an easy way to set a Property-grid to a read only mode. If I disable a PropertyGrid this also prevents the user from scrolling the list. So I'm thinking the best way to do this is to add ReadOnlyAttributes to the properties at run-time. Is there some other way?
I have found a very quick solution for thoses who do not care about the propertygrid being grayed out.
TypeDescriptor.AddAttributes(myObject, new Attribute[]{new ReadOnlyAttribute(true)});
propertyGrid1.SelectedObject = myObject;
Since you are implementing ICustomTypeDescriptor there is no need to add any attributes; you can just override IsReadOnly on the PropertyDescriptor. I'm thinking it should be pretty simple to write an intermediary type that mimics (via ICustomTypeDescriptor and TypeConverter) a wrapped type but always returns readonly PropertyDesciptor instances? Let me know if you want an example (it isn't trivial though).
You might also want to check whether something like this offers it built it.
My advice would be to write a custom control that inherits from the propertygrid control, and in that custom control, have a boolean value of readonly, and then override some things and check, if(readonly) then cancel the action
I ran into this one. I wanted a control that was read only but not grayed out.
Inherit from the property grid control and create your own read only version by adding the following code to override key presses
#Region "Non-greyed read only support"
Private isReadOnly As Boolean
Public Property [ReadOnly]() As Boolean
Get
Return Me.isReadOnly
End Get
Set(ByVal value As Boolean)
Me.isReadOnly = value
End Set
End Property
Protected Overrides Function ProcessDialogKey(ByVal keyData As Keys) As Boolean
If Me.isReadOnly Then Return True
Return MyBase.ProcessDialogKey(keyData)
End Function
Public Function PreFilterMessage(ByRef m As Message) As Boolean
If m.Msg = &H204 Then 'WM_RBUTTONDOWN
If Me.isReadOnly Then Return True
End If
Return False
End Function
#End Region
I ended up inheriting from PropertyGrid and select the parent category whenever a property is selected.
Simple and no need to use TypeDescriptor.
public class ReadOnlyPropGrid : PropertyGrid
{
public ReadOnlyPropGrid()
{
this.ToolbarVisible = false; // categories need to be always visible
}
protected override void OnSelectedGridItemChanged(SelectedGridItemChangedEventArgs e)
{
if (e.NewSelection.GridItemType == GridItemType.Property)
{
if (e.NewSelection.Parent != null && e.NewSelection.Parent.GridItemType == GridItemType.Category)
{
this.SelectedGridItem = e.NewSelection.Parent;
return;
}
}
}
}
Related
In C# or Vb.Net, using managed or unmanaged code, how I could retrieve the owner Form of a NotifyIcon?
I've checked the base types of NotifyIcon Class and also the ComponentConverter trying to find out a possible type-casting to obtain the Form, but I was not able to.
I also seen the NotifyIcon.ContextMenuStrip.FindForm() function, but for any reason when I assign a contextmenustrip the FindForm() function always returns a null-reference exception, and anyways even if it could work will not be a safe approach because I could have a notifyicon without a contextmenu.
My intention is to pass a NotifyIcon as an argument to some methods that will perform common tasks to save me time and reduct code.
Pseudo example:
Public Shared Sub MinimizeToSystray(ByVal ntfy As NotifyIcon)
If (ntfy Is Nothing) Then
Throw New ArgumentNullException(paramName:="ntfy")
ElseIf (ntfy.Icon Is Nothing) Then
Throw New ArgumentException(message:="The NotifyIcon doesn't have an icon.",
paramName:="ntfy")
Else
Dim f As Form = ntfy.GetOwnerForm()
f.WindowState = FormWindowState.Minimized
f.Hide()
ntfy.Visible = True
End If
End Sub
The FindForm() method can only find the form for controls. The kind that derive from Control and are embedded in a form through its Parent property. But NotifyIcon is not a control, it is Component. A Component only has a Site property, its value is only defined at design time.
There is a casual relationship between a component and the form, Winforms promises to automatically dispose any components that have a constructor overload that takes an IContainer argument. Not all them do, OpenFormDialog and BackgroundWorker for example don't, NotifyIcon does. They omit the constructor when they don't need disposal.
Which makes it technically possible to find the form back. You'd need to iterate Application.OpenForms(). And use reflection to iterate their private components collection. Do note that this can only work when the form was actually opened, its Show() method must have been called.
That's a solution that scores -100 points, it is both ugly and error prone. The simple and always-correct solution is to just add an extra argument to the method to allow passing the "owner" form. With the assumption that, since the caller needs to know the NotifyIcon instance, it also should know the form. Typically Me.
Component doesn't have a FindForm method like controls and if you need such property you should customize the component and apply a workaround. Unfortunately NotifyIcon is sealed and can not be inherited.
As mentioned by Ivan Stoev in comments, you can use Tag property to store a reference to the container form.
But If you are looking for a designer based solution that works without writing such initialization codes to set the Tag, as a good option, you can create an extender component that adds a property ParentForm to your NotifyIcon component and set the property at design-time, then you can simply use it at run-time and when setting the ParentForm property at design-time you can set Tag property in component code, and then use it at run-time.
And here is the usage:
Add a the ComponentExtender (see the code at the end of post) to your project, then build the project. Then:
Add the ComponentExtender from toolbox to your form
Set the ParentForm on componentExtender1 property of your notifyIcon1 at designer using property grid:
Use this code to find the parent form of your notify icon:
var parent = this.notifyIcon1.Tag as Form`
Code
Here is an implementation that I used and works properly:
[ProvideProperty("ParentForm", typeof(NotifyIcon))]
public class ComponentExtender
: System.ComponentModel.Component, System.ComponentModel.IExtenderProvider
{
private Hashtable components;
public ComponentExtender() : base() { components = new Hashtable(); }
public bool CanExtend(object extendee)
{
if (extendee is Component)
return true;
return false;
}
public Form GetParentForm(NotifyIcon extendee)
{
return components[extendee] as Form;
}
public void SetParentForm(NotifyIcon extendee, Form value)
{
if (value == null)
{
components.Remove(extendee);
component.Tag = null;
}
else
{
components[extendee] = value;
component.Tag = value;
}
}
}
Context
I've been working on a custom collection editor / designer for a custom ASP.Net web control. The web control exposes a strange hierarchy, so a custom editor seemed like the right thing to do to make it easier for developers.
Building ASPX code and using the web control works. In other words, things like PersistChildren and ParseChildren are taken care of.
The signature of the property in the web control looks something like this:
[PersistenceMode(PersistenceMode.InnerProperty)]
[Themeable(false)]
[Browsable(false)]
public virtual DimensionsCollection Dimensions { get; internal set; }
Note that the property is not public; if it were public, all kinds of things in the designer will go wrong. DimensionsCollection is a class that simply inherits List<Dimension>. The Dimension class itself is nothing fancy, just a thing with some properties.
Just because I think it looks cool, I want to be able to modify the property from an action in the designer. To do that, I implemented a ControlDesigner class and added an ActionList. One of the actions there is a linkbutton that opens an editor:
var editor = new Editors.DimensionEditor(control.Dimensions);
if (editor.ShowDialog() == DialogResult.OK)
{ /* SEE BELOW */ }
The editor itself is a windows form that takes a List<Dimension> as constructor argument and modifies the collection.
Problem
When I use this code, I can see that the editor works and that the control collection is updated in the 'designer' view. If I open the editor multiple times, the state changes, meaning that somewhere in memory the state is updated by the editor.
However, if I go to the ASPX code, I can see that the Dimensions are not there anymore. So, the problem in a nutshell is that I somehow have to tell Visual Studio to write/serialize/persist the property to the ASPX file. (simple as that...)
Strangely, I cannot find anywhere how to do this... even though a normal CollectionEditor seems to be capable of doing just that (which I cannot subclass unfortunately)
Some things I tried
For other properties I noticed you have to use something like this, but this doesn't seem to work. Code was entered at the point marked as 'see below' or in some cases to a helper call in the designer called from that point:
PropertyDescriptor pd = TypeDescriptor.GetProperties(base.Component)["Dimensions"];
// use setter with internal property -> no effect
// this.OnComponentChanged(this, new ComponentChangedEventArgs(this.Component, pd, null, newdim)); -> no effect
// use getter to obtain list -> populate that using another list that's created in the editor
I can understand why it doesn't work; apparently someone has to tell Visual Studio that the property has changed... I just don't know how to do just that.
This was really a pain to figure out with apparently no sources online that explain how to do this.
Basically you want to use the OnComponentChanging / Changed methods to notify the designer. And apparently the designer uses transactions for the rest of the logic. (My guess is that it has to do with undo/redo behavior). For a normal type this is done automatically when you use the PropertyDescriptor, for collections it apparently doesn't wrap the collection which means you have to do it manually.
To solve the issue, you need to create a small method like this in either the UITypeEditor or in the DesignerActionList class your implementing:
private void ChangeAction(List<Dimension> newDimensions)
{
IDesignerHost host = GetService(typeof(IDesignerHost)) as IDesignerHost;
PropertyDescriptor pd = TypeDescriptor.GetProperties(typeof(MyControl))["Dimensions"];
var dimensions = (DimensionsCollection)pd.GetValue(control);
var trans = host.CreateTransaction();
IComponentChangeService ccs = (IComponentChangeService)GetService(typeof(IComponentChangeService));
ccs.OnComponentChanging(control, pd);
dimensions.Clear();
dimensions.AddRange(newDimensions);
ccs.OnComponentChanged(control, pd, null, dimensions);
trans.Commit();
}
If you're implementing a UITypeEditor, make sure to use context.Instance from EditValue as the control and the given provider to lookup the services.
Let's say I have a property which I want shown in a DataGridView, but not when the same object is shown in a PropertyGrid. I know I can use [Browsable(false)], but that hides it in both views. I can also do a gridView.Columns["blah"].Visible = false;, but this is the opposite of what I want, as it hides in the DataGridView but not in PropertyGrid. Is there some way to do the reverse? (Short of creating a whole new DataTable just to hold the same data minus one field, and rebinding everything to that instead - that's really a kludge way to do things.) Alternatively, I could live with a solution which adds a column to the DataGridView that is not present on the actual class.
it is possible to solve this issue by using the BrowsableAttributes property of a PropertyGrid.
First, create a new attribute like this:
public class PropertyGridBrowsableAttribute : Attribute
{
private bool browsable;
public PropertyGridBrowsableAttribute(bool browsable){
this.browsable = browsable;
}
}
Then add this attribute to all those properties which you want to be shown in your PropertyGrid:
[DisplayName("First Name"), Category("Names"), PropertyGridBrowsable(true)]
public string FirstName {
get { return ... }
set { ... }
}
Then set the BrowsableAttributes property like this:
myPropertyGrid.BrowsableAttributes = new AttributeCollection(
new Attribute[] { new PropertyGridBrowsableAttribute(true) });
This will only show the attributed properties in your property grid and the DataGridView can still access all properties with only a little bit more coding effort.
I would still go with Tergiver and call this behaviour a bug, since the documentation of the Browsable attribute clearly states its use for property windows only.
(Credit goes to user "maro" at http://www.mycsharp.de/wbb2/thread.php?postid=234565)
I am working on a quiz control in asp.net with dynamically created questions and options.
The main control is basically a container to hold all of the questions.
In design view users can add questions through a custom Collection Editor.
Everytime i add a question to the collection editor list it generates a question tag for me.
Inside each question object is a label and a n amount of Option objects that inherit the Radiobutton Control. Each of these Option objects in turn represent a option the user can select for each question.
This all works except i am now at the part where i want to be able to read the Checked value of each radiobutton. When i want to implement this quiz inside a page and check the questions i want to put a button in this page and call the following function that is inside the control:
$
public String checkQuestions()
{
if (questions != null)
{
foreach (Question question in questions)
{
options = question.readOptions();
int i = 0;
foreach (Option option in options)
{
testLabel.Text = option.Checked.ToString(); // test purposes only
}
}
}
return errors;
}
However once i select a radiobutton and click on the submit button the Checked value will always turn out false for all of the options.
Basically it is losing its checked value after a Postback and i am just stuck in trying to solve it.
Would appreciate it if anyone could point me in the right direction.
At a first glance, there are two things I'd check. Firstly, make sure you're implementing IPostBackDataHandler. this requires you to implement two methods, LoadPostData and RaisePostDataChangedEvent. At my first guess, the first one is probably the source of your problem.
Handling postback manually
LoadPostData takes a string postDataKey and a NameValueCollection postCollection and returns a bool indicating whether or not the value has changed as a result of the postback. You don't need to implement this the way .Net originally intends, for example I created a control that held several radio buttons (that for reasons that aren't important here couldn't simply be a RadioButtonList control) and so made sure they were all named by a property string GroupName and inspected the postCollection for that GroupName:
public bool LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection)
{
bool oldValue = _isChecked;
postCollection = HttpContext.Current.Request.Form; // See note below
_isChecked = (postCollection[this.GroupName] == this.Text);
return oldValue == _isChecked;
}
You'll notice that I'm redefining the postCollection here; this is because postCollection only contains a subset of the HttpRequest.Form corresponding to what ASP.Net thinks your control should care about. As you're also building a composite control here, you probably want to do the same.
Don't worry if this doesn't work first time round; it's worth stepping through what gets passed into this method in debug mode (or outputting things to the HttpContext.Trace, which I often find easier) to see why your code isn't quite what you need.
A quick caveat
One last thing: LoadPostData is only called if the posted form contains a field with a name which matches the UniqueID of your control. As your control is a composite control, you might want to cowboy this slightly, like so:
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
writer.WriteBeginTag("input");
writer.WriteAttribute("type", "hidden");
writer.WriteAttribute("name", this.UniqueID);
writer.WriteAttribute("value", "post");
writer.Write(" />");
}
It's a dirty hack, but it'll work ;o)
Handling viewstate manually
If handling the postback manually doesn't solve your problem, it might be that you need to mess with the viewstate of your control. Don't worry, this is nowhere near as scary as it seems, provided you follow a few simple rules.
To handle your viewstate manually, you just need to override two methods called, obviously enough, LoadViewState and SaveViewState. The first takes an object of viewstate to inflate and the other returns that same object structure. If you make your SaveViewState override return something containing the structure you need to save all the important properties that need persisting, then you just inflate it again in your LoadViewState method.
Here's where the first of the cunning tricks comes up. There are certain datatypes that you should use for saving viewstate and you should never use any other type (because other types are stored really inefficiently). The types that will probably be most useful to you are System.Web.UI.Pair, System.Web.UI.Triplet and our old friends System.Collections.ArrayList and System.Collections.Hashtable. Pairs and Triplets simply store two or three values of type object; ArrayLists are effectively a List<object>.
I'd guess that, in your circumstance, you probably want to store either (1) an ArrayList of boolean flags, storing the "checkedness" of your radiobuttons or (2) an ArrayList of strings or ints, storing the IDs or index of the checked radiobuttons.
In the control I mentioned earlier, I just needed to store the checkedness and the Text property, so my LoadViewState and SaveViewState methods looked like this:
protected override void LoadViewState(object savedState)
{
Pair state = savedState as Pair;
if (state != null)
{
_isChecked = state.First as Nullable<bool> ?? false;
this.Text = state.Second as string;
}
}
protected override object SaveViewState()
{
return new Pair(_isChecked, this.Text);
}
Again, if this doesn't work first time, you almost certainly want to step through the code or throw things into the Trace. Importantly, you probably want to avoid throwing Exceptions from these methods, in case your viewstate is corrupt or non-existent or something.
Further reading on viewstate
There are a couple of very useful articles I keep bookmarked for when I'm messing with viewstate. The first one explains about why you should only store certain types in the viewstate (like using ArrayList and Hashtable, rather than List<T> and Dictionary<TKey, TValue>) and the second is a good in-depth explanation of how all this viewstate stuff actually works.
Don't let the BinaryFormatter get at it!
Truly understanding ViewState
I hope all this helps resolve your problem.
I'm looking for a way to set up my own default property values for different types of controls in my C# .NET windows application. The default property values should 'override' the existing default values of the controls, but still be 'overridable' by setting the property values explicitly in the designer.
This is to simplify the process of changing default appearance/behaviour of controls when the client (or myself) change their mind for the 10th time. This relates especially to controls like the DataGridView or 3rd party controls where there are tons of layout-related properties to maintain.
I am aware of the ability to create inherited controls and use the DefaultValue attribute, but this is not the solution I'm looking for for a couple of reasons:
It's a hassle having to inherit of every type of control I want to specify custom properties for, not to mention overriding/shadowing the properties and setting the DefaultValue attribute.
I can no longer use the standard .NET controls, but have to use the inherited controls.
The number of inherited controls increases over time and clutters up the toolbox.
Myself or other developers on the project forget to use the new inhertied types in times of haste, resulting in inconsitent behaviour/appearance of controls.
This is how I imagined that it will work:
Example 1: A DataGridView by default has background color
SystemColors.Window. I set my own
default value to Color.Blue (how
outrageous!). In the designer, the
default background color is used,
i.e. the background color is not set
explicitly in the .designer.cs file.
When running the application, a portion of code is executed, causing the grid to turn
blue, as specified by me.
Example 2: The background color of the same DataGridView is set to
Color.Red in the designer. This
overrides my own default value of blue, showing a red background
in the grid, both in design-time and run-time.
Solution
The solution for me was to use reflection to check the DefaultValue attribute, as suggested by Daniel Brückner.
I recurse through all controls on a form, calling SetDefaultValues for each control. For each property value to set, I call the SetValue method, which makes sure only properties that haven't been changed from their default values, are set.
There is one flaw in this approach, though. Properties that have been set explicitly in the designer, but do not differ from their default values, will be overwritten by the SetValue method.
void SetDefaultValues(Control control)
{
if (control is DataGridView)
{
SetValue(control, "BackColor", Color.Blue);
}
else if (control is TextBox)
{
// etc.
}
}
private static void SetValue(object control, string propertyName, object newValue)
{
System.Reflection.PropertyInfo prop = control.GetType().GetProperty(propertyName);
if (prop == null)
{
throw new ArgumentException(string.Format(
"Specified property \"{0}\" does not exist on type \"{1}\".", prop.Name, control.GetType().FullName),
"propertyName");
}
bool defaultValueFound = false;
object defaultValue = null;
foreach (object attr in prop.GetCustomAttributes(true))
{
if (attr is DefaultValueAttribute)
{
defaultValue = ((DefaultValueAttribute)attr).Value;
defaultValueFound = true;
break;
}
}
if (!defaultValueFound && prop.PropertyType.IsValueType)
{
// Get default value for value types if no default value was specified by attributes:
defaultValue = Activator.CreateInstance(prop.PropertyType);
}
if (defaultValue == null || defaultValue.Equals(prop.GetValue(control, null)))
{
// If default value matches current value, set new value:
prop.SetValue(control, newValue, null);
}
}
While not as pretty as generics, you might me able to do something with Control Builders to pull this off.
Edit:
Last night I did a quick prototype of a generic wrapper control with the ControlBuilder. I am not happy with the results. While you can probably get it to work, I beleive a new Page or Container class might be a much simpler result. The source code I used in my test is avalible on my blog.
There are several solutions I have used or I can think of.
Inheriting the control, but you mentioned that already.
Some more advanced control libraries (like DevExpress) have the build-in ability to load the layout from configuration files (XML in the case of DevExpress) or are even completly skinnable (true for DevExpress, too).
Sometimes I create extension method for the controls and call them in the constructor of the user control or form. This is an easy way to enable or disable sets of functionalities like sorting. multiselect, or column reordering in data grids and gives a consistent behavior and look.
Use data binding and bind the properties to some configuration data. I believe there is even a build in functionality - user settings or something like that - but I have never used this feature.
Calling the extension method on all controls like proposed above is not very handy in larger projects. You could recursivly visit all controls in a form when it is created, look at the properties, compare to the default value (using reflection to get the DefaultValue attribute), and if they don't match (that is the value has been overriden in the designer) load your default value from some file or in-memory store and apply it.
you could overide page instead and have a loop through all the controls e.g.
foreach (Control c in Page.Controls)
{
if (c is Textbox)
{
(Textbox)c.Color.blah.blah.blah ;)
}
///etc
Recurse through (c.Controls);
}