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 ...
Related
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;
}
}
I want to inject a MenuStrip Item or create a new one if it doesn't exists (but this is a different question) from the base constructor, the logic is the following:
public class LocalizedForm : Form
{
public LocalizedForm()
{
Shown += (sender, e) =>
{
MenuStrip menu = null;
if (HasValidMenu(out menu))
LanguageManager.AttachMenu(menu); //Language Manager will inject MenuStripItems to the passed MenuStrip
};
}
protected bool HasValidMenu(out MenuStrip menu)
{
try
{
menu = Controls.OfType<MenuStrip>().SingleOrDefault(x => x.Dock == DockStyle.Top);
return menu != null;
}
catch
{
menu = null;
return false;
}
}
}
and two childs:
public partial class frmMain : LocalizedForm
{
}
public partial class frmCredentials : LocalizedForm
{
}
Why I use Shown event? That's because the MenuStrip will not be found in the Controls until it is rendered or loaded.
Well, my main problem now is that the menu options are created twice instead one time.
As you can see there:
By this reason I have changed the following things:
protected bool HasValidMenu(out MenuStrip menu) to protected bool HasValidMenu(string name, out MenuStrip menu)
The call has been changed to:
if (HasValidMenu(out menu)) to if (HasValidMenu(GetType().Name, out menu))
This will return the main type name from where this was called.
And the magic:
menu = Controls.OfType<MenuStrip>().SingleOrDefault(x => x.Dock == DockStyle.Top); to
menu = Application.OpenForms[name].Controls.OfType<MenuStrip>().SingleOrDefault(x => x.Dock == DockStyle.Top);
But for some reason, Shown event doesn't affect to derived classes. I have testing by calling the Load or Shown event of derived classes by using Application.OpenForms[name] but this is also null.
So, what can I do?
I have a single page that has a number of controls configured a certain way depending on some condition (e.g. if it is a user accessing the page or an admin). How I currently achieve this is by having an interface for the settings which are common to all pages, and then extending classes which implement the properties specific to the type of user.
For example:
public interface Display_Type
{
string backgroundColor { get; }
}
public class AdminPage : Display_Type
{
string backgroundColor { get { return "orange"; } }
}
public class UserPage : Display_Type
{
string backgroundColor { get { return "blue"; } }
}
And my page's codebehind:
public partial class MyPage : System.Web.UI.Page
{
Display_Type pageSettings;
protected void Page_Load(object sender, EventArgs e)
{
if ((bool)Session["Is_Admin"])
{
pageSettings = new AdminPage();
}
else
{
pageSettings = new UserPage();
}
// ...
string backgroundColor = pageSettings.backgroundColor;
// ... Do stuff with background color
}
}
This works fine for me, but since these settings are constant across the application, they seem to make more sense as static classes. However, I'm having trouble figuring out how to do this because I can't assign a static class to a variable.
My questions are:
Is there a better way I can accomplish what I'm trying to do here?
Or, if this is okay, how could I accomplish what I'm doing with static classes / members?
It may be worth noting that the user/admin example is not how I'm using this structure in my web application, and in fact has nothing to do with the user themselves but rather other factors such as request parameters.
Put your settings on the BasePage and have other pages derive from it.
You will set the settings only once.
public abstract class MyBasePage : System.Web.UI.Page
{
protected Display_Type PageSettings { get; private set; };
protected void Page_Load(object sender, EventArgs e)
{
if ((bool)Session["Is_Admin"])
{
PageSettings = new AdminPage();
}
else
{
PageSettings = new UserPage();
}
}
}
public partial class MyPage : MyBasePage
{
protected void Page_Load(object sender, EventArgs e)
{
// ...
string backgroundColor = PageSettings.backgroundColor;
// ... Do stuff with background color
}
}
This is not how I would do this, but if you are doing it this way, static classes have absolutely nothing to do with and no use in this situation.
You might consider having a single instance of each of the Display_Type classes stored somewhere to be reused, instead of creating a new one each time, though. This might end up being as a static variable... but that's not the same at all as a static class.
You can use a singleton to define your AdminPage and UserPage profile and add a static method GetDisplayType() to your implementation.
public static class PageTypes {
public static PageType Admin(/** stuff here */);
public static PageType User(/** stuff here */);
}
public class PageType {
readonly string _backgroundColor;
public PageType (/** stuff here */) {
_backgroundColor = backgroundColor;
}
public string BackgroundColor {
get {
return _backgroundColor;
}
}
So now, you can access them like this in your method:
if ((bool)Session["Is_Admin"])
{
PageSettings = PageTypes.Admin;
}
else
{
PageSettings = PageTypes.User;
}
I agree with Jakub about using the base class to prevent code duplication.
1. Is there a better way I can accomplish what I'm trying to do here?
Microsoft .NET has a built-in application settings implementation: setting files. Just use them if your settings aren't configurable by each user.
Application settings can be defined in a satellite assembly and overridden in the app.config or web.config of your application.
That's I find better to define such settings using setting files, which is a built-in, understandable and well-implemented solution and you've it out-of-the-box.
How to accomplish your goal using setting files?
You can use configuration by convention, and your background color settings will look like this:
AdminPage_BackgroundColor => #000000
UserPage_BackgroundColor => #FFFFFF
and so on
In your case, you've two background color settings for both pages, but if you'd need to configure background color for any of pages, you'd do this in your Page-derived class instance:
Properties.PagesStyle.Default[GetType().Name + '_' + "BackgroundColor"]
And you'll be able to get background color by page and from your settings file.
Let's implement this in your page class:
public partial class MyPage : System.Web.UI.Page
{
Color backgroundColor = null;
protected void Page_Load(object sender, EventArgs e)
{
if ((bool)Session["Is_Admin"])
{
backgroundColor = Properties.PagesStyle.Default.AdminPage_BackgroundColor;
}
else
{
backgroundColor = Properties.PagesStyle.Default.UserPage_BackgroundColor;
}
// ... Do stuff with background color
}
}
Note settings files allow you to define strongly-typed values. Let's say you typed "UserPage_BackgroundColor" as System.Drawing.Color, designer will edit color by using a color picker, and when you access to this setting, you'll get a System.Color instead.
Before begin with another approach, check these useful links:
Settings files in MSDN: http://msdn.microsoft.com/en-us/library/aa730869(v=vs.80).aspx
Some other answer I did some time ago: Can a class library have an App.config file?
Recommended approach
There's a solid, stable and valuable way of styling Web sites: CSS.
Instead of creating your own styling approach, just play with CSS classes.
For example, in some CSS stylesheet you define both background colors for admin page and user page:
body.UserPage
{
background-color: #000;
}
body.AdminPage
{
background-color: #FFF;
}
Just imagine you've this ASP.NET page (I'll include standard XHTML markup only):
<html>
<head></head>
<body id="body" runat="server"></body>
</html>
In your code-behind code perhaps you can do this:
public partial class MyPage : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if ((bool)Session["Is_Admin"])
{
body.Attributes["class"] = "AdminPage";
}
else
{
body.Attributes["class"] = "UserPage";
}
}
}
This way you avoid:
Creating redundant settings since you use CSS as styling mechanism (don't reinvent the wheel!).
Compiling styling values: it's a client-side thing, because it's CSS.
To answer your second question, a implementation of a static solution:
public interface IDisplayType
{
string backgroundColor { get; }
}
public class AdminPage : IDisplayType
{
public string backgroundColor { get { return "orange"; } }
}
public class UserPage : IDisplayType
{
public string backgroundColor { get { return "blue"; } }
}
public static class PageProperties
{
private static AdminPage _adminPage = new AdminPage();
private static UserPage _userPage = new UserPage();
public static IDisplayType DisplayType { get
{
if ((bool)HttpContext.Current.Session["Is_Admin"])
{
return _adminPage;
}
return _userPage;
}
}
I also changed the type Display_Type to IDisplayType which gives a beter description of what it is.
You could then use the following code in your page.
string backgroundColor = PageProperties.DisplayType.backgroundColor;
I would like to set the one template for edit/insert and view in my custom FormView control . But i got these odd exception
Unable to cast object of type 'System.Web.UI.LiteralControl' to type 'System.Web.UI.WebControls.Table'.
public class CustomFormView : FormView
{
[PersistenceMode(PersistenceMode.InnerProperty), TemplateContainer(typeof(FormView), BindingDirection.TwoWay)]
public IBindableTemplate FormTemplate { get; set; }
protected override void OnInit(EventArgs e)
{
ChangeMode(FormViewMode.Edit);
if (FormTemplate != null)
{
if (CurrentMode == FormViewMode.Edit)
{
FormTemplate.InstantiateIn(this);
}
}
base.OnInit(e);
}
}
edited :
in the first step , I created the new user control and added a formview ("FV")
public partial class Form : UserControl
{
private IBindableTemplate _template = null;
[PersistenceMode(PersistenceMode.InnerProperty),
TemplateContainer(typeof(FormView), System.ComponentModel.BindingDirection.TwoWay)]
public IBindableTemplate FormTemplate { set;get }
protected void Page_Init()
{
if (FormTemplate != null)
{
FV.InsertItemTemplate = FV.EditItemTemplate = FormTemplate;
if (!IsPostBack) FormTemplate.InstantiateIn(FV);
}
}
}
Now , I want to convert this user control to web control .
I would appreciate it if you could reply my question.
What exactly are you trying to do??
Whatever it is you're trying to do, you're doing it wrong.
TemplateContainer(typeof(FormView)) This is not possible.
You need to provide your own type there, inheriting from IDataItemContainer.
Edit:
I wouldn't recommend putting all this effort just because you want to have 1 template for edit and insert. Better put the same contents in both templates. Experience learns that over time you will want distinct functionallity for edit and insert.
I have added an EventHandler for the Click-event to a picturebox but on runtime this handler is never called (the debugger shows me that it is added to the control directly but when I click on the picturebox nothing happens).
I assume it has something to do with my inheritance. I have a usercontrol called AbstractPage (its not really abstract since the designer doesnt like that) which only consists of a heading and this picturebox but it provides quite some functions the actual pages rely on.
#region Constructor
public AbstractPage()
{
InitializeComponent();
lblHeading.Text = PageName;
picLock.Click += new EventHandler(picLock_Click);
}
#endregion
#region Events
void picLock_Click(object sender, EventArgs e)
{
...do some stuff
}
#endregion
The page implementations just inherit this class and add their controls and behavior. We recently figured out that subclassing UserControl is not performant and we lose some performance there, but its the best way to do it (I dont want to c&p function for 25 pages and maintain them).
My pageA looks like this
public partial class PageA : AbstractPage
{
#region Constructor
public PageA()
{
// I dont call the base explicitely since it is the
// standard constructor and this always calls the base
InitializeComponent();
}
#endregion
public override string PageName
{
get { return "A"; }
}
public override void BindData(BindingSource dataToBind)
{
...
}
Anyway, the picLock_Click is never called and I dont know why?
The pages are all put into a PageControl which consists of a TreeView and a TabContainer where the pages are put once I call addPage(IPage)
public partial class PageControl {
...
protected virtual void AddPages()
{
AddPage(new PageA());
AddPage(new PageD());
AddPage(new PageC());
...
}
protected void AddPage(IPage page)
{
put pagename to treeview and enable selection handling
add page to the tabcontainer
}
Thanks in advance
If I understand your problem correctly, this worked for me out of the box (using VS2k8). My code:
public partial class BaseUserControl : UserControl
{
public BaseUserControl()
{
InitializeComponent(); //event hooked here
}
private void showMsgBox_Click(object sender, EventArgs e)
{
MessageBox.Show("Button clicked");
}
}
public partial class TestUserControl : BaseUserControl
{
public TestUserControl()
{
InitializeComponent();
}
}
I moved the TestUserControl to a form, clicked the button and got the message box as expected. Can you paste some more code, e.g. how do you use your AbstractPage?
I found the problem. We are using the Infragistics WinForms but in that case I used the standard picturebox. I replaced it with the UltraPictureBox and now it works.