C# 3.5 Winforms...
So I’ve recently discovered the IExtenderProvider and how it can be used to extend controls with additional properties.
In a prototype project that I setup i put a break point on the ‘set’ method for my extendee property and as the form loads I could see the ‘set’ method executing for every control on the form; which is exactly what I wanted. Following the successful prototype I implemented the extender component into my main project. All the forms in my project inherit from a base form which I’ve added my extender component to. On the base form I set the modifier of the extender component to public so that its accessible by the form inheriting this base form.
Doing the same thing before i added a break point on the ‘set’ method for my extendee property but the method doesn’t execute for the controls in the form (but only for the controls in the base form). HELP!
I should probably add at this point that i’ve source controlled my forms and so most of them are checked-in (ie lock from modification). For the forms that I’ve checked out and modified the provider property; I’ve noticed in the designer file that all controls have an additional statement which calls the ‘set’ method of the provider property.
this.MyProvider1.SetMyProperty(this.txtTextBox1, false);
Am I right in thinking that for the extender component to work it has to physically modify the designer file or should it be able to cope with locked files and therefore call the set method dynamically? I guess if it does have to modify the designer file then this isn’t a problem for new forms or forms that get modified after the extender component was added to the project – but it would be problem when you have 101 forms all locked by source-safe...
I’d appreciate any thoughts...
At what point does the extender provider (IExtenderProvider) extend the 'type' (in my case a winforms control) that the extender was intended for; at design time or at run time?
The designer is responsible for showing you the properties of the extender in the property editor
Method bool CanExtend(object) from the IExtenderProvider interface
Am I right in thinking that for the extender component to work it has to physically modify the designer file or should it be able to cope with locked files and therefore call the set method dynamically?
It has to physically modify the designer file, and write the extended properties there
I guess if it does have to modify the designer file then this isn’t a problem for new forms or forms that get modified after the extender component was added to the project – but it would be problem when you have 101 forms all locked by source-safe...
This is is not a problem for new forms, and not for old forms.
If you want to set some extended properties, open the old form and set the extended properties (a check out of the file is necessary)
This really does confirm my suspicions, many thanks. But this does leave a problem in that the components are only extended if some physical change is made to the old form.
I was trying to hijack the Set property method to also add and remove an event handler to the component (if the component was a control). Image the property is a Boolean and when set to false it adds the event handle and therefore the default behaviour (setting to true doesn’t add and event handler)
To cut a long story short the controls which were part of newly added forms automatically have an event handler added even without me explicitly setting the property to false but the designer file of the old forms never modifier so the event handler wasn’t added.
As some background, I was trying to add a global event handler for all controls
Global event handler for all controls for User Help
The theme here is to add context help to my forms here’s example of the extender ( the event handler is added as part of the end initialiser)
public partial class HelpProvider : Component, IExtenderProvider, ISupportInitialize
... other code of the extender omitted ...
#region ISupportInitialize Members
public void BeginInit()
{
// do nothing
}
public void EndInit()
{
if (DesignMode)
return;
foreach (Component item in _disableOnlineHelp)
{
if (item == null)
continue;
if (GetDisableOnlineHelp(item)) // developer has decide to set property to TRUE
continue;
Control control = item as Control;
if (control != null)
continue;
control.HelpRequested += new HelpEventHandler(HelpProvider_HelpRequested);
_toolTip.SetToolTip(control, GetHelpText(control));
}
}
#endregion
#region DisableOnlineHelp Provider Property
public virtual bool GetDisableOnlineHelp(Component component)
{
object flag = _disableOnlineHelp[component];
if (flag == null)
return false;
return (bool)flag;
}
public virtual void SetDisableOnlineHelp(Component component, bool value)
{
_disableOnlineHelp[component] = value;
}
#endregion
One issue might be the foreach loop in the EndInit method:
Control control = item as Control;
if (control != null)
continue;
If the item is, in fact, a Control, you get out of the loop before executing this code:
control.HelpRequested += new HelpEventHandle(HelpProvider_HelpRequested);
_toolTip.SetToolTip(control, GetHelpText(control));
so you never add the Event Handler or the ToolTip, to any Control. Oops :)
Thanks,
John
Related
I am implementing context-sensitive help for an existing WinForms app built in Visual Studio .NET. I have added a HelpProvider to the form and set the HelpNamespace property to a wonderful .chm that covers every control and menu item on the form. I have set the necessary HelpKeyword on all the controls that derive from Control and so far all is great: F1 works perfectly.
My problem is that I can't work out how to do it for menu items. These use the ToolStripMenuItem class, which does not derive from Control and so has no HelpKeyword property. How should I provide context-sensitive help for individual menu items? Mr. Google has not been very forthcoming.
Using F1 is not a common way of providing help for menu items. Menu items usually use ToolTip, or show some help text in StatusBar or usually their comprehensive helps comes with Help content of main page.
I prefer to use one of above mentioned solutions, but here for learning purpose, I'll show what you can do using HelpRequested event of the form.
To handle help for form and controls, you can rely on the HelpRequested event of the form and controls.
Here you can rely on Form event to solve the problem. Since you have a HelpProvider on form, you should know HelpProvider handles HelpRequested event of all controls internally and, for controls having ShowHelp set to true, it sets Handled to true and prevents bubbling the event up so you can not have your custom code for handling help event if ShowHelp is true. So you should set ShowHelp for controls to false and just use HelpProvider as a help key holder.
To solve the problem using the HelpRequested event of the form, you should follow these steps:
For ToolStripMenuItems, use the Tag property as the help key holder.
For other controls, if you use HelpProvider to assign HelpKey, don't forget to set ShowHelp to false.
Handle the HelpRequested event of the form.
In the body of event handler, check if there is an active menu item on your form, then use the Tag property of the active item to show help. If there is not any active menu, use the ActiveControl property of the form to show the help.
Example
Here is a step by step example of how you can show help for menu items using F1 key. To do so, follow these steps:
Create Form, Menu and Controls - Create a Form and put some controls and a MenuStrip having some menu and sub menus on the form.
Configuring HelpProvider - Put a HelpProvider control on form and for each control assign suitable key to HelpKeyword property of control. Also set ShowHelp for each control to false. We will handle help in code.
Configuring Help for Menu - For a ToolStripMenuItem use its Tag property to store the help keyword.
Creating a helper method to find descendants of the Menu - Add a class to your application having the following code. In the following code, I've introduced an extension method to get all sub ToolStripMenuItem of a MenuStrip:
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
public static class ToolStripMenuItemExtensions
{
public static List<ToolStripMenuItem> Descendants(this MenuStrip menu)
{
var items = menu.Items.OfType<ToolStripMenuItem>().ToList();
return items.SelectMany(x => Descendants(x)).Concat(items).ToList();
}
public static List<ToolStripMenuItem> Descendants(this ToolStripMenuItem item)
{
var items = item.DropDownItems.OfType<ToolStripMenuItem>().ToList();
return items.SelectMany(x => Descendants(x)).Concat(items).ToList();
}
}
Handling the Helprequested event to show help - Handle the HelpRequested event of the form and implement the algorithm which I described above using the following code:
private void Form1_HelpRequested(object sender, HelpEventArgs hlpevent)
{
string keyword = "";
var selectedMenuItem = this.menuStrip1.Descendants()
.Where(x => x.Selected).FirstOrDefault();
if (selectedMenuItem != null)
keyword = selectedMenuItem.Tag?.ToString();
else if (ActiveControl != null)
keyword = helpProvider1.GetHelpKeyword(ActiveControl);
if (!string.IsNullOrEmpty(keyword))
Help.ShowHelp(this, "Help.chm", HelpNavigator.Index, keyword);
}
Note
For testing the solution you don't need a chm file having index and so on. You can simply show the helpkeyword in Text property of form. It means the solution is working and after that you can create suitable chm file.
You can use one of the other overloads of ShowHelp method of Help class based on your requirement.
There are HelpKeyword and HelpString extended properties for controls, pay attention which one you are using and get the same one in the HelpRequested event.
Don't forget to set ShowHelp to false. If you forget this step, the event will be handled internally in Helpprovider.
Don't forget to assign a help keyword to Tag property of menu items. To make it more friendly for future, you can simply create an extender provider that adds a help keyword property to menu items.
I have a custom control derived from ListView (say MyListView). In the designer mode I define several ListViewGroups in it. Unfortunately, if I later use this control on a form, whenever I open the designer for this form, it adds the same set of groups to the MyListView control. So after some editing there is a big number of duplicate groups in it.
It seems the form designer (not surprisingly) cannot recognize that the groups were already added in the MyListView constructor and not in the form itself, so it should not add the code to generate them in InitializeComponent(). Can I prevent this somehow?
This is because you added the groups in the constructor, which also runs at design time, and their values are getting saved in the form's Designer.cs file. The constructor runs too early so you cannot see what groups will be added, later, by InitializeComponent(). And it runs too early to get a reliable indication that the code runs in design mode, the DesignMode property is still false.
The proper fix is to give the control its own designer but that is very painful, especially so for ListView. The cheap workaround is to postpone adding the groups and using an event that runs after InitializeComponent. The HandleCreated event is good for that. Like this:
class MyListView : ListView {
protected override void OnHandleCreated(EventArgs e) {
base.OnHandleCreated(e);
if (this.DesignTime && this.Groups.Count == 0) {
// Add the groups here
//...
}
}
}
I have a relatively simple setup. I have a custom usercontrol that has a bunch of components on it, some text boxes, and a listview.
In the designer, I can drag and drop other controls into my usercontrol, and it adds them to the usercontrol instance. I don't want this.
How can I explicitly say "Don't allow additional controls to be added to this usercontrol?"
That's not the way it works. When you drop your user control on a form then adding controls to it isn't supported. That requires a special designer, this answer shows what is required. Maybe it looks like the controls get added but they merely overlap your user control. Their parent is still the form.
If a programmer opens your user control class itself in the designer then, sure, he can add controls as he pleases. The only way to stop that is to not ship the source code and use the sealed keyword to prevent deriving from it.
You could create a boolean property MyContainer.DisableAddControls or something.
If your MyContainer.Controls.Add(..) is overridden, then you can throw some custom exception in that Add() method as follows:
if(DisableAddControls)
{
throw new DisableAddControlsException();
}
If you are inheriting that method straight from ContainerControl, then you can handle the ControlAdded event and throw the exception there.
myContainer.ControlAdded += myContainerControlAdded;
private void Control_Added(object sender, System.Windows.Forms.ControlEventArgs e)
{
if(DisableAddControls)
{
throw new DisableAddControlsException();
}
}
On second thought, this won't throw out your designer at design time... nevermind.
Currently I have a C# program with a windows form and then a user control template put onto the form. The user control template is really just used as a placeholder. I have a series of other controls which inherit from this user control template.
Each of those controls have navigation buttons like 'Continue' and 'Back' on them and each control knows which control needs to be loaded next. However what I need to figure out is an easier way to have variables that are global to these controls.
The only workaround I have is that I pass the form to each control when they are loaded and use variables inside of the form to read and write to. What would be the proper way to have each of these user control screens be built off of a base control which contained objects all of the controls could get to?
Sorry for the rambling nature of the post but I've been thinking about this problem all morning.
Here is some of the code:
Most of what I have written was based on hiding and showing the user controls so that content in the controls wouldn't be lost during navigation. I won't be needing to do that as eventually it will be loading the fields of data from a database.
Code for initially loading control from form click:
conTemplate1.Controls.Clear();
conInbound Inbound = new conInbound(this);
Inbound.Dock = DockStyle.Fill;
Inbound.Anchor = (AnchorStyles.Left | AnchorStyles.Top);
conTemplate1.Controls.Add(Inbound);
Code for Continue button inside of one of the controls:
if ((Parent.Controls.Count - 1) <= Parent.Controls.IndexOf(this))
{
UserControl nextControl = new conPartialClear();
nextControl.Dock = DockStyle.Fill;
Parent.Controls.Add(nextControl);
this.Hide();
Parent.Controls[Parent.Controls.IndexOf(this) + 1].Show();
}
else
{
this.Hide();
Parent.Controls[Parent.Controls.IndexOf(this) + 1].Show();
}
The best-practice for communicating from a control to a parent is to use events, and for communicating from a parent to a control is to call methods.
However, if you don't want to or can't follow this practice, here's what I would recommend.
Each UserControl has a ParentForm property that returns the Form that contains the control. If you know that the UserControl will always be attached to MyParentForm, you just cast the ParentForm and then you can access all public controls, methods, etc.
Here's what I mean:
public class conTemplate
{
public MyParentForm MyParentForm
{
get
{
return (MyParentForm)this.ParentForm;
}
}
}
This way, you can easily access any public members of MyParentForm. Your conInbound class could have code such as this.MyParentForm.GlobalSettings.etc..., and could even have access to any public controls.
I'm not totally sure I understand your problem. It sounds like you want the user control to "do something" with it's parent form. If that's the case, you may want to consider adding events to the UC and then handle them on the form itself.
Basically, for your UC's "continue", you'll have an event that's fired when it's pressed. You'll want to handle that in your form. I'm not real sure about the syntax from memory, or I'd work something out for you code-wise. But I think that's the route you'll want to take. Think of your UC like any other windows form control. If you add a button to your form, you assign it it's event method. Do the same with the UC.
I found this and thought it may be helpful. Scroll down to where it talks about UC's and events.
http://www.akadia.com/services/dotnet_user_controls.html
Hope this helps.
EDIT after new info from OP.
You could declare a global variable inside the UC of type yourForm and then set that variable to the ParentForm at run-time, if I'm understanding you correctly.
So, inside your UC Class, you could do:
private parentFormInstance;
then inside the constructor of the UC, you could set it as such:
parentFormInstance = this.ParentForm; (or whatever the property name is).
This allows you at design-time to use:
parentFormInstance.DoSomething();
without the compiler yelling at you.
Just basic advice, but if you can go back and make it easier on yourself, even if it takes some additional time re-working things, it'd be worth it. It may save you time in the long run.
I have an object that starts a thread, opens a file, and waits for input from other classes. As it receives input, it writes it to disk. Basically, it's a thread safe data logging class...
Here's the weird part. When I open a form in the designer (Visual Studio 2008) that uses the object the file gets created. It's obviously running under the design time vhost process...
The odd thing is I've not been able to reproduce the issue in another project. I'm not sure what the rules are for code that gets executed in the designer and code that does not. For example, creating a file in a Windows Forms constructor doesn't actually create the file at design time...
What is the explanation? Is there a reference?
The constructor of a control or form does not get executed when editing that class in the designer (nor does OnLoad get called). I've occasionally used this to set one value in the designer (eg. making its child controls all Visible in the designer) but override some of them to a different default value in the constructor (eg. hiding certain child controls which will only show in certain circumstances, such as an indicator on a status bar).
However, the constructor does get executed if the control is placed as a child on another control or form in the designer. OnLoad gets executed as well. This may be how your logging code was getting accidentally triggered in the designer.
For detecting design vs runtime, an answer to another question has screenshots of some emperical tests showing the values returned by some common approaches. It appears that a child control of a child control (two levels down) of the form or control being edited in the designer sees its own DesignMode == false, so the normal property check will fail to protect code (eg. in the OnLoad method) for controls nested within a control added in the designer. If you were checking DesignMode as one would expect, it could be the nesting which caused it to get around that check. It also always sees DesignMode == false within the constructor.
Also, note that the LicenseManager.UsageMode check only sees DesignTime within the constructor; when OnLoad is called it is within a RunTime LicenseContext. The most complete solution seems to be to check LicenseManager.UsageMode in the constructor of the control or form (or component) and save the setting to a member variable or property which you can check later to avoid running code that should never run in the designer even when nested. There's also another approach in another answer to that other question which accounts for nesting but only works outside the constructor.
You can check the UsageMode of the LicenseManager, to check if the code is in design time or not.
System.ComponentModel.LicenseManager.UsageMode == System.ComponentModel.LicenseUsageMode.Designtime
Here is a quick example:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace Test
{
public class ComponentClass : Component
{
public ComponentClass()
{
MessageBox.Show("Runtime!");
}
}
}
When this component gets add to your form in the designer, you will immediatly get a message box.
To prevent this you can add a simple if statement to check if the code is not in design time
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace Test
{
public class ComponentClass : Component
{
public ComponentClass()
{
if (LicenseManager.UsageMode != LicenseUsageMode.Designtime)
{
MessageBox.Show("Runtime!");
}
}
}
}
After adding the if statement, the messagebox no longer appears when the component is added to the form via the designer.
Well, since this has been resurrected anyway, here's the function I use to determine whether I'm in design mode:
public static bool IsAnyInDesignMode(Control control){
while(control != null){
if(control.Site != null && control.Site.DesignMode)
return true;
control = control.Parent;
}
return false;
}
This handles the case where the control is a child created by another control. The DesignMode property is only set for controls created by the designer itself.
You could also use this to check if the Visual Studio Designer is running the code:
public static bool DesignMode
{
get { return (System.Diagnostics.Process.GetCurrentProcess().ProcessName == "devenv"); }
}
Then in Form_Load:
if (!DesignMode)
{
// Run code that breaks in Visual Studio Designer (like trying to get a DB connection)
}
However, this is less elegant than using the LicensManager.UsageMode, but it works (until Microsoft changes the name of the process Visual Studio runs under).
There are some things you shouldn't do with the designer. I don't have any hard evidence, but I found that the Windows Forms designer hates it when you take away the default constructor from it. Just go ahead and create new overloads, but leave the empty constructor in place.
Also try to avoid doing Form_Load events in base classes you inherit from.