|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionI started this project when I needed something like the toolbar in FireFox's Options dialog in one of my other projects. This is where the name came from. I looked around but couldn't find any control that had what I needed so I decided to write my own. Another benefit of writing this control was that it gave me the opportunity to play around with some of the techniques used in control creation, i.e., Designers, I took a lot of hints from Matthew Hall's Themed Taskbar for general design and use of collections, and I also borrowed John O' Byrne's excellent Imagelistpopup control, modified it slightly and used it in my image dropdown editor. Using the controlLike any other .NET control, for use in the IDE, you should add the There are two primary controls in the
A Once an instance of MozPaneThe There are several properties that define the appearance and behavior of the
The most useful events are:
All private void mozPane1_ItemSelected(object sender, Pabo.MozBar.MozItemEventArgs e)
{
// Check the tag..
switch(e.MozItem.Tag)
{
case "Save":
{
break;
}
case "Load":
{
break;
}
}
}
The To select an item, use the methods MozItemA A
Each of the states can have a different image, borderstyle and color for background and border. The images are set through the item's An item can also have different styles, that are set using the
When set to Nested propertiesA nice way to organize related properties in a control is to group them together in a nested structure, common examples of this in controls are
To accomplish this, we need to do two things. First, we need a class ( [TypeConverter(typeof(PaddingCollectionTypeConverter))]
public class PaddingCollection
{
private MozPane m_pane;
private int m_horizontal;
private int m_vertical;
public PaddingCollection(MozPane pane)
{
// set the control to which the collection belong
m_pane = pane;
// Default values
m_horizontal = 2;
m_vertical = 2;
}
[RefreshProperties(System.ComponentModel.RefreshProperties.All)]
[Description("Horizontal padding.")]
public int Horizontal
{
get
{
return m_horizontal;
}
set
{
m_horizontal = value;
if (m_pane!=null)
{
// padding has changed , force DoLayout
m_pane.DoLayout();
m_pane.Invalidate();
if (m_pane.PaddingChanged!=null)
m_pane.PaddingChanged(this,new EventArgs());
}
}
}
[RefreshProperties(System.ComponentModel.RefreshProperties.All)]
[Description("Vertical padding.")]
public int Vertical
{
get
{
return m_vertical;
}
set
{
m_vertical = value;
if (m_pane!=null)
{
m_pane.DoLayout();
m_pane.Invalidate();
if (m_pane.PaddingChanged!=null)
m_pane.PaddingChanged(this,new EventArgs());
}
}
}
}
The class is pretty straightforward but the important things are the attributes. The [TypeConverter(typeof(PaddingCollectionTypeConverter))]
public class PaddingCollection
{
}
The [RefreshProperties(System.ComponentModel.RefreshProperties.All)]
public int Horizontal
{
}
Since this is a nested property, the type converter should inherit from public class PaddingCollectionTypeConverter : ExpandableObjectConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context,
Type sourceType)
{
if(sourceType == typeof(string))
return true;
return base.CanConvertFrom (context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context,
Type destinationType)
{
if(destinationType == typeof(string))
return true;
return base.CanConvertTo (context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext
context, System.Globalization.CultureInfo culture, object value)
{
if(value.GetType() == typeof(string))
{
// Parse property string
string[] ss = value.ToString().Split(new char[] {';'}, 2);
if (ss.Length==2)
{
// Create new PaddingCollection
PaddingCollection item =
new PaddingCollection((MozPane)context.Instance);
// Set properties
item.Horizontal = int.Parse(ss[0]);
item.Vertical = int.Parse(ss[1]);
return item;
}
}
return base.ConvertFrom (context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context,
System.Globalization.CultureInfo culture,
object value, Type destinationType)
{
if(destinationType == typeof(string) &&
(value is MozPane.PaddingCollection) )
{
// cast value to paddingCollection
PaddingCollection dest = (PaddingCollection)value;
// create property string
return dest.Horizontal.ToString()+"; "+dest.Vertical.ToString();
}
return base.ConvertTo (context, culture, value, destinationType);
}
}
All that's left to do is to create a
Custom image selectorAnother nice thing to provide with controls is custom type editors. Each of the image states in
For this to work, we must create our own type editor ( public class ImageMapEditor : System.Drawing.Design.UITypeEditor
{
}
We must override public override System.Drawing.Design.UITypeEditorEditStyle
GetEditStyle(System.ComponentModel.ITypeDescriptorContext context)
{
if(context != null && context.Instance != null )
{
return UITypeEditorEditStyle.DropDown ;
}
return base.GetEditStyle (context);
}
We must also override public override bool
GetPaintValueSupported(System.ComponentModel.ITypeDescriptorContext context)
{
return true;
}
To initiate the editing of the property, we override public override object
EditValue(System.ComponentModel.ITypeDescriptorContext context,
IServiceProvider provider, object value)
{
wfes = (IWindowsFormsEditorService)
provider.GetService(typeof(IWindowsFormsEditorService));
if((wfes == null) || (context == null))
return null ;
ImageList imageList = GetImageList(context.Instance) ;
if ((imageList == null) || (imageList.Images.Count==0))
return -1 ;
m_imagePanel = new ImageListPanel();
m_imagePanel.BackgroundColor = Color.FromArgb(241,241,241);
m_imagePanel.BackgroundOverColor = Color.FromArgb(102,154,204);
m_imagePanel.HLinesColor = Color.FromArgb(182,189,210);
m_imagePanel.VLinesColor = Color.FromArgb(182,189,210);
m_imagePanel.BorderColor = Color.FromArgb(0,0,0);
m_imagePanel.EnableDragDrop = true;
m_imagePanel.Init(imageList,12,12,6,(int)value);
// add listner for event
m_imagePanel.ItemClick += new ImageListPanelEventHandler(OnItemClicked);
// set m_selectedIndex to -1 in case
// the dropdown is closed without selection
m_selectedIndex = -1;
// show the popup as a drop-down
wfes.DropDownControl(m_imagePanel);
// return the selection (or the original value if none selected)
return (m_selectedIndex != -1) ? m_selectedIndex : (int) value ;
}
Finally, we need to override public override void PaintValue(System.Drawing.Design.PaintValueEventArgs pe)
{
int imageIndex = -1 ;
// value is the image index
if(pe.Value != null)
{
try
{
imageIndex = (int)Convert.ToUInt16( pe.Value.ToString() ) ;
}
catch
{
}
}
// no instance, or the instance represents an undefined image
if((pe.Context.Instance == null) || (imageIndex < 0))
return ;
// get the image set
ImageList imageList = GetImageList(pe.Context.Instance) ;
// make sure everything is valid
if((imageList == null) || (imageList.Images.Count == 0)
|| (imageIndex >= imageList.Images.Count))
return ;
// Draw the preview image
pe.Graphics.DrawImage(imageList.Images[imageIndex],pe.Bounds);
}
When the custom type editor is done, all we need to do is add the [TypeConverter(typeof(ImageTypeConverter))]
[Editor(typeof(MozBar.ImageMapEditor),typeof(System.Drawing.Design.UITypeEditor))]
[Description("Image for normal state.")]
public int Normal
{
get
{
return m_imageIndex;
}
set
{
if (value != m_imageIndex)
{
m_imageIndex = value;
if (m_item.ImageChanged!=null)
m_item.ImageChanged(this,
new ImageChangedEventArgs(itemState.Normal));
m_item.Invalidate();
}
}
}
Theme support
Implementing theme support basically involves four steps:
To find out if the system supports themes, we need to check which version of ComCtl32.dll is in use. This is done by using its // Setup struct and function call
[StructLayout(LayoutKind.Sequential)]
public struct DLLVERSIONINFO
{
public int cbSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformID;
}
[DllImport("Comctl32.dll", EntryPoint="DllGetVersion", ExactSpelling=true,
PreserveSig=false, CharSet=CharSet.Unicode)]
private static extern int DllGetVersion(ref DLLVERSIONINFO s);
public bool _IsAppThemed()
{
try
{
// Check which version of ComCtl32 thats in use..
DLLVERSIONINFO version = new DLLVERSIONINFO();
version.cbSize = Marshal.SizeOf(typeof(DLLVERSIONINFO));
int ret = DllGetVersion(ref version);
// If MajorVersion > 5 themes are allowed.
if (version.dwMajorVersion >= 6)
return true;
else
return false;
}
catch (Exception)
{
return false;
}
}
Almost all theme related functionality is found in the uxTheme.dll. This is an unmanaged DLL and since Microsoft does not supply any managed wrapper, we need to use [DllImport("uxTheme.dll", EntryPoint="GetThemeColor", ExactSpelling=true,
PreserveSig=false, CharSet=CharSet.Unicode )]
private extern static void GetThemeColor (System.IntPtr hTheme,
int partID,
int stateID,
int propID,
out int color);
[DllImport( "uxtheme.dll", CharSet=CharSet.Unicode )]
private static extern IntPtr OpenThemeData( IntPtr hwnd, string classes );
[DllImport( "uxtheme.dll", EntryPoint="CloseThemeData", ExactSpelling=true,
PreserveSig=false, CharSet=CharSet.Unicode) ]
private static extern int CloseThemeData( IntPtr hwnd );
To intercept the protected override void WndProc(ref Message m)
{
base.WndProc (ref m);
switch (m.Msg)
{
case WM_THEMECHANGED:
{
// Theme has changed , get new colors if Theme = true
if (Theme)
GetThemeColors();
break;
}
}
}
OK, so now we have our functions, we know when the theme has changed and if visual themes are supported. The only thing left to do is to get the theme related info we want and possibly adjust it to fit our needs. private void GetThemeColors()
{
int EPB_HEADERBACKGROUND = 1;
int EPB_NORMALGROUPBACKGROUND = 5;
int TMT_GRADIENTCOLOR1 = 3810;
int TMT_GRADIENTCOLOR2 = 3811;
Color selectColor = new Color();
Color focusColor = new Color();
Color borderColor = new Color();
bool useSystemColors = false;
// Check if themes are available
if (m_themeManager._IsAppThemed())
{
if (m_theme!=IntPtr.Zero)
m_themeManager._CloseThemeData(m_theme);
// Open themes for "ExplorerBar"
m_theme = m_themeManager._OpenThemeData(this.Handle,"EXPLORERBAR");
if (m_theme!=IntPtr.Zero)
{
// Get Theme colors..
selectColor = m_themeManager._GetThemeColor(m_theme,
EPB_HEADERBACKGROUND,1,TMT_GRADIENTCOLOR2);
focusColor = m_themeManager._GetThemeColor(m_theme,
EPB_NORMALGROUPBACKGROUND,1,TMT_GRADIENTCOLOR1);
borderColor = ControlPaint.Light(selectColor);
selectColor = ControlPaint.LightLight(selectColor);
focusColor = ControlPaint.LightLight(selectColor);
}
}
// apply colors..
ItemColors.SelectedBorder = selectColor;
ItemColors.Divider = borderColor;
this.BorderColor = borderColor;
ItemColors.SelectedBackground = selectColor;
ItemColors.FocusBackground = focusColor;
ItemColors.FocusBorder = selectColor;
Invalidate();
}
Known problems/issues
History
ConclusionI hope this control can be of use to you. I'm sure there is plenty of room for improvements and added functionality, so if you have any ideas or suggestions please post a comment. | ||||||||||||||||||||