65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 MonoDevelop 和 GTK# 的自定义控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.40/5 (9投票s)

2008年5月18日

GPL3

5分钟阅读

viewsIcon

132072

downloadIcon

1787

在 Linux 上使用 Mono 实现可移动控件,并进行自定义渲染。

引言

通常,Mono 和 GTK 的自定义控件是使用基本控件(Widgets)完成的。有时,为了实现更高级的 GUI,会选择自定义渲染。本文介绍了一个在面板内实现可移动对象的简单实现,并带有一个弹出菜单来修改每个对象及其外观。

背景

我最近切换到了 Ubuntu,并且对此非常满意。我主要在 .NET 上进行开发,而我对某些 Windows 工具的依赖是我一直等待的原因。现在这已经不再是什么问题了,这要归功于 Mono 和 MonoDevelop 团队以及 GTK#、Cairo 和 Pango 等相关库的出色工作。

Mono 将 .NET 平台带到了 Linux,而 MonoDevelop 提供了 Visual Studio 的良好替代方案,使得在 GTK 桌面上的 GUI 应用程序开发几乎毫不费力。

我编写了一个我日常使用的协作工具,在切换平台后,我的第一个目标是将其移植到 Mono 和 GTK#。在由于 Windows Forms 和 GTK# 之间的根本差异导致的一些调整之后,我不得不承认,应用程序的移植比我最初想象的要容易得多;我用小部件替换了 UI 控件,设置了各种窗体使用 GTK# 布局,仅此而已。其余的非 UI 代码按预期工作,整体性能良好。

让我最困难的领域是使用行为与基本 GTK 组件不同的自定义控件。这也是本文的重点。

目标

  • 元素的可视化表示,如同图表
  • 每个元素都有一个标题和一些在渲染中显示的属性
  • 每个元素都可以在定义的面板中自由移动
  • 每个元素都有自己的弹出菜单来更改其属性

解决方案

在花了一些时间研究 GTK 文档后,FixedDrawingArea 小部件似乎是显而易见的 Au choix。Fixed 面板允许控件以绝对位置进行放置。尽管通常不推荐在大多数窗体中使用它,但在本例中它效果很好。

MVPanel 是继承自 Fixed 的容器,用于容纳可移动对象。它可以像普通的 GTK 小部件一样放置在任何窗体中。它包含以下方法:

  • AddMovingObject(string name,string caption, int x, int y) 用于添加新的可移动对象。
  • RefreshChildren() 用于强制控件重绘。

MVObject 是自定义控件的基本实现,它将在 MVPanel 中可移动。它继承自 DrawingArea

它包含以下方法/属性:

  • ShowMenu() 向用户显示该特定控件的选项。
  • Edit() 将控件设置为编辑模式。
  • Redraw() 强制控件重绘。
  • Caption 用于获取控件的基本属性。

渲染自定义控件

DrawingArea 小部件的渲染必须完全指定。这是自定义的缺点,但有时是必需的。Cairo(用于图形)和 Pango(用于文本)库似乎是推荐使用的,以呈现一致且现代的外观。本示例使用了 Pango,但未使用 Cairo,因为渲染相当简单。

重写的 OnExposeEvent 方法是所有渲染发生的地方。在这里,区域用深灰色和浅灰色绘制,并用一些黑线分隔。在每个彩色区域中还添加了一些文本。

using System;
using Gtk;

namespace GtkControl.Control
{
    
    public class MVObject : Gtk.DrawingArea 
    {
        
        public MVObject(string pName, string cap)
        {
                  ...
        }
                
        public void Redraw()
        {
            this.QueueDraw();
        }

               private Pango.Layout GetLayout(string text)
        {
            Pango.Layout layout = new Pango.Layout(this.PangoContext);
            layout.FontDescription = Pango.FontDescription.FromString("monospace 8");
            layout.SetMarkup(&quot;<span color=\&quot;black\&quot;>&quot; + text + &quot;</span>&quot;);
            return layout;
        }
        
        protected override bool OnExposeEvent (Gdk.EventExpose args)
        {    
            Gdk.Window win = args.Window;
            Gdk.Rectangle area = args.Area;
                
            win.DrawRectangle(Style.DarkGC(StateType.Normal), true, area);
            win.DrawRectangle(Style.MidGC(StateType.Normal),true,0,15,1000,1000);
            win.DrawRectangle(Style.BlackGC,false,area);
            win.DrawLine(Style.BlackGC,0,15,1000,15);            
            win.DrawLayout(Style.BlackGC,2,2,titleLayout);

            if (!string.IsNullOrEmpty(body))
            {
                win.DrawLayout(Style.BlackGC,2,17,GetLayout(body));
            }
            return true;
        } 
    }
}

QueueDraw 方法是对 GTK 的调用,指示控件需要重绘。

移动控件

默认情况下,控件无法移动。但是,Fixed 小部件允许将控件放置或移动到指定位置。此外,控件需要响应鼠标单击、拖动和按钮释放。

using System;
using Gtk;

namespace GtkControl.Control
{
    public partial class MVPanel : Gtk.Bin
    {
               private Widget currCtrl = null;
        private Widget currClone = null;
        private int origX = 0;
        private int origY = 0;
        private int pointX = 0;
        private int pointY = 0;

        ...
        
        //Mouse click on the controls of the panel  
        protected void OnButtonPressed(object sender, ButtonPressEventArgs a)
        {        
            //Save the origin of the move in origX and origY
            currCtrl = sender as Widget;
            currCtrl.TranslateCoordinates(this.fixed1,0,0,out origX, out origY);
                      //Save the pointer position relative the origin of the move
            fixed1.GetPointer(out pointX,out pointY);
        }

        protected void OnButtonReleased(object sender, ButtonReleaseEventArgs a)
        {
            //Final destination of the control
            MoveControl(currCtrl, a.Event.X,a.Event.Y,false);
            currCtrl = null;
            if (currClone!=null)
            {
                this.fixed1.Remove(currClone);
                currClone.Destroy();
                currClone = null;
            }
        }

        //Called whenever a control is moved
        protected virtual void OnFixed1MotionNotifyEvent (object o, 
                               Gtk.MotionNotifyEventArgs args)
        {
            //Rendering of a clone at the desired location
            if (currCtrl!=null)
            {
                MoveClone(ref currClone, args.Event.X,args.Event.Y);
            }
        }
    }    
}

TranslateCoordinates 是一个 GTK 方法,它返回控件在容器(此处为 fixed1)中的相对位置。

GetPointer 也是一个 GTK 方法,它返回光标相对于控件的位置。

MoveControl 方法调用 fixed1.Move 方法,并确保控件保留在面板内。它还负责在控件移动后重绘控件。

MoveClone 方法调用 MoveControl 对选定小部件的克隆进行操作。这确保用户在两个位置(原点和目标)都能看到控件。克隆在按下按钮时生成,并跟随鼠标移动直到按钮释放。如果不需要中间状态,可以删除 MotionNotifyEvent 事件。

DrawingArea 对象进行渲染,但不响应事件。这就是为什么 MVObject 被嵌入到 EventBox 中,所有鼠标事件都在其中进行控制。这在 GetMovingBox 方法中完成。

//Create the event box for the custom control
private EventBox GetMovingBox(string name, string caption)
{ 
    MVObject ctrl = new MVObject(name,caption);
    EventBox rev = new EventBox();
    rev.Name = name;
    rev.Add(ctrl);
    Console.WriteLine(&quot;Creating new moving object&quot;+rev.Name);
    return rev;
}

使用 MVPanel

公共方法 AddMovingObject(string name,string caption, int x, int y) 是将对象添加到面板的方式,其中 caption 是标题,name 是对象的标识。

//Add a movable control to the panel
public void AddMovingObject(string name,string caption, int x, int y)
{
    //Prevent the object to be displayed outside the panel
    if (x<0)
    {
        x = 0;
    }
    if (y<0)
    {
        y = 0;
    }
    //Create the box where the custom object is rendered
    EventBox ev = GetMovingBox(name,caption);
    //Add the events to control the movement of the box
    ev.ButtonPressEvent+=new ButtonPressEventHandler(OnButtonPressed);
    ev.ButtonReleaseEvent+=new ButtonReleaseEventHandler(OnButtonReleased);
    //Add the control to the panel
    this.fixed1.Put(ev,x,y);
    this.ShowAll();
}

对于上面的截图,它翻译为:

this.mvpanel1.AddMovingObject(&quot;mv1&quot;,&quot;Moving Object 1&quot;,10,10);
this.mvpanel1.AddMovingObject(&quot;mv2&quot;,&quot;Moving Object 2&quot;,10,55);
this.mvpanel1.AddMovingObject(&quot;mv3&quot;,&quot;Mono&quot;,10,100);
this.mvpanel1.AddMovingObject(&quot;mv4&quot;,&quot;Gtk#&quot;,10,145);
this.mvpanel1.AddMovingObject(&quot;mv5&quot;,&quot;MonoDevelop&quot;,10,190);
this.mvpanel1.AddMovingObject(&quot;mv6&quot;,&quot;Pango&quot;,10,235);
this.mvpanel1.AddMovingObject(&quot;mv7&quot;,&quot;Test&quot;,10,280);

在控件上显示上下文菜单

示例的最后一部分,控件的上下文菜单。

MVObject 实现了一个公共方法 ShowMenu,它简单地调用预定义的 Gtk.Menu 在控件上的 Popup 方法。

Gtk.Menu popup = new Gtk.Menu();
Gtk.MenuItem text1 = new MenuItem(&quot;Test1&quot;);
text1.Activated+=new EventHandler(Menu1Clicked);
Gtk.MenuItem text2 = new MenuItem(&quot;Test2&quot;);
text2.Activated+=new EventHandler(Menu2Clicked);

Menu1Clicked 更改控件上的 body 属性并强制重绘。这通过 OnExposedEvent 方法改变了其外观。

protected void Menu1Clicked(object sender, EventArgs args)
{
    body = &quot;Test1&quot;;
    this.QueueDraw();
}

菜单的调用在 MVPanel.OnButtonPress 中由鼠标右键单击定义。

//Mouse click on the controls of the panel  
protected void OnButtonPressed(object sender, ButtonPressEventArgs a)
{        
    //Right click
    if (a.Event.Button==3)
    {
        if (sender is EventBox)
        {
            ((sender as EventBox).Child as MVObject).ShowMenu();
        }    
    }
    //Left click
    else if (a.Event.Button==1)
    {
        //Double-click
        if (a.Event.Type==Gdk.EventType.TwoButtonPress)
        {
            if (sender is EventBox)
            {
                //Calling the edit method of the control
                ((sender as EventBox).Child as MVObject).Edit();
            }    
        }
        else
        {
            //Setup the origin of the move
            currCtrl = sender as Widget;
            currCtrl.TranslateCoordinates(this.fixed1,0,0,out origX, out origY);
            fixed1.GetPointer(out pointX,out pointY);
        }
    }
}

关注点

这个实现比我预期的要容易。当然,它目前的状态并不是非常有用,但它让我对在常规小部件之外使用 GTK 和 Mono 的可能性有了很好的认识。我可能会将其扩展为我自己的小型图形框架,但我确信它已经以单独项目的形式存在,并且设计更健壮。但是,经过一些额外的调整,它肯定会比我需要的多。

我想分享这段代码,因为我发现关于自定义渲染实现、带有完整代码的文档在 Mono 上非常少。

但是,关于我用作参考的基础库,有很多文档:

© . All rights reserved.