Custom Control - Clickable link in properties box - c#

I'm making a custom control using C#, and I need to add a link to the property box(so I can show a form once it's clicked).
Here's an example:

You are looking for DesignerVerb.
A designer verb is a menu command linked to an event handler. Designer
verbs are added to a component's shortcut menu at design time. In
Visual Studio, each designer verb is also listed, using a LinkLabel,
in the Description pane of the Properties window.
You can use a verb for setting value of a single property, multiple properties or for example for just showing an about box.
Example:
Create a designer for your control or for your component deriving from ControlDesigner class or ComponentDesigner (for components) an override Verbs property and return a collection of verbs.
Don't forget to add reference to System.Design.dll.
using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms;
using System.Windows.Forms.Design;
[Designer(typeof(MyControlDesigner))]
public class MyControl : Control
{
public string SomeProperty { get; set; }
}
public class MyControlDesigner : ControlDesigner
{
private void SomeMethod(object sender, EventArgs e)
{
MessageBox.Show("Some Message!");
}
private void SomeOtherMethod(object sender, EventArgs e)
{
var p = TypeDescriptor.GetProperties(this.Control)["SomeProperty"];
p.SetValue(this.Control, "some value"); /*You can show a form and get value*/
}
DesignerVerbCollection verbs;
public override System.ComponentModel.Design.DesignerVerbCollection Verbs
{
get
{
if (verbs == null)
{
verbs = new DesignerVerbCollection();
verbs.Add(new DesignerVerb("Do something!", SomeMethod));
verbs.Add(new DesignerVerb("Do something else!", SomeOtherMethod));
}
return verbs;
}
}
}

Related

How to Create such a Task Panel?

In Visual Studio 2008,
If you create a Form and put a Control on it,
you can edit the control's properties via the Properties window.
Some controls enable changing their properties in another way,
in addition to the Properties window.
It looks like this:
It seems that all controls that has this pane, has it in the same style,
meaning it's something that is provided by Visual Studio,
and the maker of the control just chooses the items to include inside,
like Fields, and Clickable Links that open some windows.
So my question:
What is the name of this pane control,
and how do I create one?
That menu is called Smart Tags or Designer Actions and you can add smart tag to your control. To do so, you need to create a custom Designer for your control and in the designer, override its ActionLists property.
Example
Let's say we have created a control having some properties, and we want to show the following properties of out control in smart tags window:
public Color SomeColorProperty { get; set; }
public string[] Items { get; set; }
And the expected result for us is:
MyControl
Here we decorate the control with Designer attribute to register the custom designer:
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using System.Windows.Forms.Design;
[Designer(typeof(MyControlDesigner))]
public partial class MyControl : UserControl
{
public MyControl()
{
InitializeComponent();
}
void InitializeComponent() { }
public Color SomeColorProperty { get; set; }
public string[] Items { get; set; }
}
MyControlDesigner
Here we override ActionLists and return a new DesignerActionListCollection containing the action list items which we need:
public class MyControlDesigner : ControlDesigner
{
private DesignerActionListCollection actionList;
public override DesignerActionListCollection ActionLists
{
get
{
if (actionList == null)
actionList = new DesignerActionListCollection(new[] {
new MyControlActionList(this) });
return actionList;
}
}
}
MyControlActionList
Here we create properties which get/set out control properties. Also we create methods which are responsible to show custom editor for some properties or do some actions. Then return a list of action items by overriding GetSortedActionItems:
public class MyControlActionList : DesignerActionList
{
ControlDesigner designer;
MyControl control;
public MyControlActionList(ControlDesigner designer) : base(designer.Component)
{
this.designer = designer;
control = (MyControl)designer.Control;
}
public Color SomeColorProperty
{
get { return control.SomeColorProperty; }
set {
TypeDescriptor.GetProperties(
(object)this.Component)["SomeColorProperty"]
.SetValue((object)this.Component, (object)value);
}
}
public void EditItems()
{
var editorServiceContext = typeof(ControlDesigner).Assembly.GetTypes()
.Where(x => x.Name == "EditorServiceContext").FirstOrDefault();
var editValue = editorServiceContext.GetMethod("EditValue",
System.Reflection.BindingFlags.Static |
System.Reflection.BindingFlags.Public);
editValue.Invoke(null, new object[] { designer, this.Component, "Items" });
}
public override DesignerActionItemCollection GetSortedActionItems()
{
return new DesignerActionItemCollection() {
new DesignerActionMethodItem(this, "EditItems", "Edit Items", true),
new DesignerActionPropertyItem("SomeColorProperty", "Some Color"),
};
}
}
For more information about this topic, take a look at this MSDN Walkthrough.
Download Example
You can download a working example from the following repository:
r-aghaei/ControlSmartTagsExample
Zip File
This is called 'DesignerActionList' or SmartTag. Smart tags are menu-like user interface (UI) elements that supply commonly used design-time options.
Step:
You must add a reference to the design-time assembly, System.Design.dll
Create DesignerActionList class and get the reference to control in the constructor.
public class MyControlTasks : System.ComponentModel.Design.DesignerActionList
{
private MyControl myControl;
private DesignerActionUIService designerActionUISvc = null;
public MyControlTasks( IComponent component ) : base(component)
{
this.myControl = component as MyControl;
this.designerActionUISvc =
GetService(typeof(DesignerActionUIService))
as DesignerActionUIService;
}
}
Add methods and properties that you want to associate to smart-tag items
Create base designer for the control
public interface IDesigner {
void Dispose();
void Initialize(IComponent component);
IComponent Component {
get;
}
}
Return a new instance of the MyControlTasks class that you created earlier.
public override DesignerActionListCollection ActionLists
{
get
{
var actionLists = new DesignerActionListCollection();
actionLists.Add(new MyControlTasks(this.Component));
return actionLists;
}
}

Custom Windows Control library in C#

How can I implement small task features in my own custom windows control library like below?
You need to create your own designer for your control. Start that by adding a reference to System.Design. A sample control could look like this:
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Windows.Forms.Design;
[Designer(typeof(MyControlDesigner))]
public class MyControl : Control {
public bool Prop { get; set; }
}
Note the [Designer] attribute, it sets the custom control designer. To get yours started, derive your own designer from ControlDesigner. Override the ActionLists property to create the task list for the designer:
internal class MyControlDesigner : ControlDesigner {
private DesignerActionListCollection actionLists;
public override DesignerActionListCollection ActionLists {
get {
if (actionLists == null) {
actionLists = new DesignerActionListCollection();
actionLists.Add(new MyActionListItem(this));
}
return actionLists;
}
}
}
Now you need to create your custom ActionListItem, that could look like this:
internal class MyActionListItem : DesignerActionList {
public MyActionListItem(ControlDesigner owner)
: base(owner.Component) {
}
public override DesignerActionItemCollection GetSortedActionItems() {
var items = new DesignerActionItemCollection();
items.Add(new DesignerActionTextItem("Hello world", "Category1"));
items.Add(new DesignerActionPropertyItem("Checked", "Sample checked item"));
return items;
}
public bool Checked {
get { return ((MyControl)base.Component).Prop; }
set { ((MyControl)base.Component).Prop = value; }
}
}
Building the list in the GetSortedActionItems method is the key to creating your own task item panel.
That's the happy version. I should note that I crashed Visual Studio to the desktop three times while working on this example code. VS2008 is not resilient to unhandled exceptions in the custom designer code. Save often. Debugging design time code requires starting another instance of VS that can stop the debugger on the design-time exceptions.
It's called a "Smart Tag". You can find a quick example of it here:
Adding Smart Tags to Windows Forms Controls
Source: How can I implement "small task features" in my own custom windows control library like below? - CodeProject

Proper Form Application Design

I'm creating a WinForm application in C# and one of its functions is displaying text in text boxes. I'm coding the logic for querying a database in a separate class and am unable to access the text box element in the class I'm creating (I'm getting a "name" does not exist in the current context error). Do I put all of my form logic into my Form1.cs file?
You should try to keep your display logic separate from the rest of the application - the simplest thing to do is have the form class handle getting/setting form values. This means your data access component will query the database and the form will have to map the output to something that can be displayed e.g.
public class Form1 : Form
{
public DataAccess Db { get; set; }
public void UpdateSomething()
{
this.textbox.Text = this.Db.GetSomeDatabaseValue();
}
}
No keep Business logical apart from UI logic. You should raise an event in the Business class and catch it in the UI form. From there display it.
If they are not try setting the modifiers in properties to public or internal.
Edit- edited to fit answer format
If you want to access the TextBox in another class change the access modifier as
public or Internal (If it is in the same assembly)
.
Default it will be private
Better you can pass the value to the business logic layer.Not the entire control,would not be good always.
B.I is to do all the business so the value of text box is enough.
Have you looked at the backgroundworker? With this, you can run things asynchronously when you click a button on your form. All of your updating things on your form would be done on your form itself. Your other code (that you were having trouble accessing Form1 from) would 'report progress.' When progress is reported, you can send any object that you want to Form1 and then in an event handler on the form you can take information from that object and update the view. You would use this, for example, to update a progress bar while keeping the UI responsive.
We are currently doing an application with a MVP pattern in winforms. We are using the bindings in winforms so the UI will update when the data does. Our forms use BindingSources and BindingLists. We bind the main BindingSource to our presenter class.
Example of Form codebehind
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SomeNameSpace.Utilities.UI;
using SomeNameSpace.Utilities.Validation;
namespace Management.UI
{
public partial class ManualControl : UserControl, IManualRaffleView
{
private ManualPresenter _presenter;
public ManualControl()
{
InitializeComponent();
}
[Browsable(false)]
public ManualPresenter Presenter
{
get
{
return _presenter;
}
set
{
_presenter = value;
if(_presenter != null)
{
_manualPresenterBindingSource.DataSource = _presenter;
_ListBindingSource.DataSource = _presenter;
_ListBindingSource.DataMember = "Something";
_KindListBindingSource.DataSource = _presenter;
_KindListBindingSource.DataMember = "SomethingElse";
_presenter.CloseView += new Action(CloseMe);
_presenter.VerifyingCancel += new Func<bool>(VerifyingCancel);
_presenter.Showing += new Action(ShowMe);
_presenter.Synchronizer = this;
}
}
}
void CloseMe()
{
this.Enabled = false;
}
private void ManualRaffleForm_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = false;
}
private void ShowMe()
{
this.Enabled = true;
}
bool VerifyingCancel()
{
return MessageBox.Show("Cancel?", Notifier.ApplicationName,
MessageBoxButtons.YesNo, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2) == DialogResult.Yes;
}
private void _cancelButton_Click(object sender, EventArgs e)
{
Presenter.HandleCancel();
}
private void _initiateButton_Click(object sender, EventArgs e)
{
Presenter.HandleInitiate();
}
private void _saveButton_Click(object sender, EventArgs e)
{
if(Presenter.Error == true.ToString())
Presenter.HandleDone();
else
_manualPresenterBindingSource.ResetBindings(false);
}
}
}
Then our Presenter implements INotifyPropertyChanged and may look something like this
namespace SomeCompany.UI
{
public class ManualPresenter : INotifyPropertyChanged, IDataErrorInfo
{
#region Fields
//fields
#endregion Fields
public string SomeFormField
{ get{ return _someFormField;}
set{
if(_someFormField != value)
{
_someFormField = value;
//Update Model if Needed
_model.SomeFormField = _someFormField;
NotifyCHanged("SomeFormField");
}
}
}
The form text boxes will bind to the properties in the presenter and any listboxes or combo boxes will bind to BindingLists.
We then use Linq to Sql for our models. There is very little logic in the Forms. Mostly just a little that is necessary for validation.

Visual Studio uses UserControlDesigner instead of CustomDesigner

I've got an ASP-UserControl QuestionWithAnswer (.ascx) : BaseQuestion : UserControl
and a ControlDesigner QuestionDesigner : UserControlDesigner.
Now i use the DesignerAttribute to associate control and designer:
[Designer(typeof(QuestionDesigner))]
public class BaseQuestion : UserControl
all types are in the same assembly (WEB Application).
But it still loads UserControlDesigner instead of mine.
Did i have to put my designer in a seperate assembly?
I suppose the asp-page designer cannot find the designer.
thx!
mo
demo code:
public class FragenDesigner : UserControlDesigner
{
private DesignerActionList _actionList;
private DesignerVerb[] _verbs;
public override DesignerActionListCollection ActionLists
{
get
{
if (_actionList == null)
{
_actionList = new DesignerActionList(new System.Windows.Forms.TextBox());
_actionList.AutoShow = true;
ActionLists.Add(_actionList);
}
return base.ActionLists;
}
}
public override DesignerVerbCollection Verbs
{
get
{
if (_verbs == null)
{
_verbs = new DesignerVerb[]
{
new DesignerVerb("test", onblabla),
};
Verbs.AddRange(_verbs);
}
return base.Verbs;
}
}
private void onblabla(object sender, EventArgs e)
{
MessageBox.Show("blabla");
}
}
okay there is already an answer: http://msdn.microsoft.com/en-us/library/system.web.ui.design.usercontroldesigner.aspx.
Remarks
There is no developer advantage to
creating your own designer derived
from UserControlDesigner. To enhance
the design-time experience for a
custom control, derive your control
from CompositeControl and your
designer from
CompositeControlDesigner. In that
case, you would not use an .ascx file
for your ASP.NET markup.
In my case there is no possibility to change to CompositeControls.
Trust me, i prefer Composite/WebControls ...

Non-abstract event defined in abstract class won't show in Designer for derived classes

I'm using the class listed below to create a UserControl wrapping a ComboBox that can accept a List<T> and return an object of type T when the internal ComboBox's selection is changed.
Everything works fine in code, exactly as I expect it to, but I can't get SelectedItemChanged event to show up in the Designer anymore when using my control. It worked fine when the abstract base class was non-abstract, but I'm trying to condense 5 essentially duplicate controls into one.
Unimportant parts have been snipped.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
namespace UserComboTest
{
public abstract partial class DropDownList<T> : UserControl where T : class
{
protected abstract int FindIndex(T item);
public abstract void Populate(List<T> items, T defaultItem);
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible), Browsable(true)]
public event EventHandler<SelectedItemEventArgs> SelectedItemChanged;
private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
{
if (null != SelectedItemChanged)
{
SelectedItemChanged(this, new SelectedItemEventArgs(Selected));
}
}
public class SelectedItemEventArgs : EventArgs
{
public SelectedItemEventArgs(T selectedItem)
{
Selected = selectedItem;
}
public T Selected { get; private set; }
}
}
public class UserDropDownList : DropDownList<User>
{
protected override int FindIndex(User user)
{
// find index for item
}
public override void Populate(List<User> users, User defaultUser)
{
// populate the list
}
}
}
EDIT: Fixed the code-breaking problem. Turned out both my namespace and form were named UserComboTest, so when it serialized the fully-qualified type name (UserComboTest.UserDropDownList), it assumed that it was a member or class under the form, not the namespace. In other words, it thought it was looking for UserComboTest.UserComboTest.UserDropDownList, which doesn't exist. Renaming the form to UserComboTest.UserComboTestForm solved that half of the problem.
Still remaining is the fact that the designer doesn't show the SelectedItemChanged event, and if I set it manually, it gets removed, so I either have to set it outside of InitializeComponent, or figure out how to get it to be serialized.
Generally, the winforms designer reacts badly to abstract base classes. You should turn the abstract methods into empty virtual methods and make the class non-abstract.

Categories