用c#和vb.net實現(xiàn)vs.net或office xp風格的菜單
小氣的神 2001.08.18
3. “menuitemstyle”接口和vs.net風格的菜單項
這個project又將切換到c#語言。我是這樣想的:先針對普通菜單、office200風格、vs.net風格三種情況定義一個統(tǒng)一的接口(interface),其中包括畫icon(drawicon)、畫分割條(drawseparator)、畫菜單背景(drawbackground)、寫菜單項的文字(drawmenutext)等功能;普通、office2000和vs.net根據(jù)各自不同的情況實現(xiàn)這個接口的drawxxx的功能。然后從menuitem繼承一個子類,象第二部分講的那樣overrides 菜單項的兩個函數(shù):onmeasureitem和ondrawitem,根據(jù)不同的風格調用上面實現(xiàn)的接口中的drawxxx函數(shù)就可以了。最后我把這部分都分隔出來放在一個.cs文件中,單獨編譯成一個vsnet.menu.dll,你只用using vsnet.menu ; 然后就可以象在第一部分那樣象使用普通的menuitem那樣來用了,demo源代碼中你還可以看到我定義了iconmenuitem的類,它有一個方法:menuitemcreator(vsnet.menu.iconmenustyle stype , string stext , bitmap bmp , system.eventhandler eh)可以完成生成需要的menuitem。本來我想用資源文件或將圖片icon等資源放在一個專門的文件中,然后由這個類來負責從資源文件或外部的類中獲得資源createmenuitem。但是是第一版,你會看到例程中我仍然用原始的new bitmap()的方式直接從硬盤拿資源。當我看到它show出來時,先是很開心,然后發(fā)現(xiàn)還有許多要改進,想想其實做一個專業(yè)的菜單也需要花許多心思。
好吧讓我們看一下有關vs.net風格菜單項這部分主要的實現(xiàn)代碼:
public class vsnetstyle : menuitemstyledrawer
{
static color bgcolor = color.fromargb(246, 246, 246);
static color ibgcolor = color.fromargb(202, 202, 202);
static color sbcolor = color.fromargb(173, 173, 209);
static color sbbcolor = color.fromargb( 0, 0, 128);
static int textstart = 20;
public void drawcheckmark(graphics g, rectangle bounds, bool selected)
{
controlpaint.drawmenuglyph(g, new rectangle(bounds.x + 2, bounds.y + 2, 14, 14), menuglyph.checkmark);
}
public void drawicon(graphics g, image icon, rectangle bounds, bool selected, bool enabled, bool ischecked)
{
if (enabled)
{
if (selected)
{
controlpaint.drawimagedisabled(g, icon, bounds.left + 2, bounds.top + 2, color.black);
g.drawimage(icon, bounds.left + 1, bounds.top + 1);
}
else
{
g.drawimage(icon, bounds.left + 2, bounds.top + 2);
}
}
else
controlpaint.drawimagedisabled(g, icon, bounds.left + 2, bounds.top + 2, systemcolors.highlighttext);
}
public void drawseparator(graphics g, rectangle bounds)
{
int y = bounds.y + bounds.height / 2;
g.drawline(new pen(systemcolors.controldark), bounds.x + systeminformation.smalliconsize.width + 7, y, bounds.x + bounds.width - 2, y);
}
public void drawbackground(graphics g, rectangle bounds, drawitemstate state, bool toplevel, bool hasicon)
{
bool selected = (state & drawitemstate.selected) > 0;
if (selected || ((state & drawitemstate.hotlight) > 0))
{
if (toplevel && selected)
{ // draw toplevel, selected menuitem
g.fillrectangle(new solidbrush(ibgcolor), bounds);
controlpaint.drawborder3d(g, bounds.left, bounds.top, bounds.width, bounds.height, border3dstyle.flat, border3dside.top | border3dside.left | border3dside.right);
}
else
{ // draw menuitem, selected or toplevel, hotlighted
g.fillrectangle(new solidbrush(sbcolor), bounds);
g.drawrectangle(new pen(sbbcolor), bounds.x, bounds.y, bounds.width - 1, bounds.height - 1);
}
}
else
{
if (!toplevel)
{ // draw menuitem, unselected
g.fillrectangle(new solidbrush(ibgcolor), bounds);
bounds.x += systeminformation.smalliconsize.width + 5;
bounds.width -= systeminformation.smalliconsize.width + 5;
g.fillrectangle(new solidbrush(bgcolor), bounds);
}
else
{
// draw toplevel, unselected menuitem
g.fillrectangle(systembrushes.menu, bounds);
}
}
}
public void drawmenutext(graphics g, rectangle bounds, string text, string shortcut, bool enabled, bool toplevel, drawitemstate state)
{
stringformat stringformat = new stringformat();
stringformat.hotkeyprefix = ((state & drawitemstate.noaccelerator) > 0) ? hotkeyprefix.hide : hotkeyprefix.show;
int textwidth = (int)(g.measurestring(text, systeminformation.menufont).width);
int x = toplevel ? bounds.left + (bounds.width - textwidth) / 2: bounds.left + textstart;
int y = bounds.top + 2;
brush brush = null;
if (!enabled)
brush = new solidbrush(color.fromargb(120, systemcolors.menutext));
else
brush = new solidbrush(color.black);
g.drawstring(text, systeminformation.menufont, brush, x, y, stringformat);
g.drawstring(shortcut, systeminformation.menufont, brush, bounds.left + 130, bounds.top + 2, stringformat);
}
}
menuitemstyledrawer就是那個公用的接口類,無論普通風格、office2000還是vs.net風格都要實現(xiàn)自己方式的接口,這個接口包括drawcheckmark、drawicon、drawmenutext、drawbackground、drawseparator等函數(shù),可以實現(xiàn)菜單項需要的各種函數(shù)。完成這部分后可以從menuitem繼承一個子類來象第二部分一樣處理了。看下面的代碼,具體考察一下熟悉的onmeasureitem和ondrawitem:
protected override void onmeasureitem(measureitemeventargs e)
{
base.onmeasureitem(e);
// make shortcut text 省略這部分代碼。
if (menustyle != iconmenustyle.standard)
{
if (text == "-")
{
e.itemheight = 8;
e.itemwidth = 4;
return;
}
int textwidth = (int)(e.graphics.measurestring(text + shortcuttext, systeminformation.menufont).width);
e.itemheight = systeminformation.menuheight;
if (parent == parent.getmainmenu())
e.itemwidth = textwidth - 5; // 5 is a magic number
else
e.itemwidth = math.max(160, textwidth + 50);
}
}
iconmenustyle.standard是個enum表明是普通風格、office2000或是vs。net的風格。這部分和我們第二部分看到的沒有什么不同。
protected override void ondrawitem(drawitemeventargs e)
{
base.ondrawitem(e);
graphics g = e.graphics;
rectangle bounds = e.bounds;
bool selected = (e.state & drawitemstate.selected) > 0;
bool toplevel = (parent == parent.getmainmenu());
bool hasicon = icon != null;
style.drawbackground(g, bounds, e.state, toplevel, hasicon);
if (hasicon)
style.drawicon(g, icon, bounds, selected, enabled, checked);
else
if (checked)
style.drawcheckmark(g, bounds, selected);
if (text == "-")
{
style.drawseparator(g, bounds);
}
else
{
style.drawmenutext(g, bounds, text, shortcuttext, enabled, toplevel, e.state);
}
}
剛剛我們說的menuitemstyledrawer接口的好處在這里顯示出來,整個過程顯得簡單明了,具體實現(xiàn)得代碼不是很多。當這個類完成后,剩下來的就是使用了它了,這部分象第一部分所述,你可以在一個頂級菜單項的子菜單項聲明成iconmenu類型的也就是我們實現(xiàn)的繼承menuitem的類,簡單的代碼象下面這樣:
private system.windows.forms.menuitem mitems1 ; system.drawing.bitmap bitmap1 = new bitmap( bmppathstr + "open.bmp") ;
mitems1 = imenuitem.menuitemcreator( menustyle , "&open" , bitmap1,
這個mitem1就是一個vs.net風格的菜單項了。具體的可以看附帶的project和屏幕截圖。
至此我們完成了用vb.net或c#完成一個有vs.net或office xp風格的菜單。三個部分是漸進的,如果你以前進行或實驗過第二部分討論的問題,那么第三部分唯一讓人感興趣的是menuitemstyledrawer接口的思路。對于整個新的.net的編程方式上說,原來的vb用戶可能會經(jīng)歷一個痛苦的過程,他們的第一反應是sub class、hook或是終極的api,而接觸過c++、mfc、delphi甚至vj++的用戶會很容易想到繼承,特別時delphi和vj的用戶入手應當最快了。想想會開始懷念以前vb的時光,因為對于這樣的問題,vb用戶總是拿著大錘,直接敲個大洞,然后拿到結果;而c++、mfc、dephi用戶則拿著一本說明書,一步一步按指示找到結果;結果可能一樣,但兩者的方式是截然不同的。好了,這些是題外話了。