ZetCode

GTK# 中的自定义小部件

最后修改于 2023 年 10 月 18 日

工具包通常只提供最常见的小部件,如按钮、文本小部件、滑块等。没有哪个工具包能提供所有可能的小部件。

更专业的小部件由客户端程序员创建。他们使用工具包提供的绘图工具来完成。有两种可能性:程序员可以修改或增强现有的部件,或者可以从头开始创建自定义部件。

刻录部件

这是一个我们从头开始创建的小部件的例子。 可以在各种媒体刻录应用程序中找到此小部件,例如 Nero Burning ROM。

burning.cs
using Gtk;
using Cairo;
using System;
 
class Burning : DrawingArea
{

    string[] num = new string[] { "75", "150", "225", "300", 
        "375", "450", "525", "600", "675" };

    public Burning() : base()
    {
        SetSizeRequest(-1, 30);
    }

    protected override bool OnExposeEvent(Gdk.EventExpose args)
    {
        
        Cairo.Context cr = Gdk.CairoHelper.Create(args.Window);
        cr.LineWidth = 0.8;

        cr.SelectFontFace("Courier 10 Pitch", 
            FontSlant.Normal, FontWeight.Normal);
        cr.SetFontSize(11);

        int width = Allocation.Width;

        SharpApp parent = (SharpApp) GetAncestor (Gtk.Window.GType);        
        int cur_width = parent.CurValue;

        int step = (int) Math.Round(width / 10.0);

        int till = (int) ((width / 750.0) * cur_width);
        int full = (int) ((width / 750.0) * 700);

        if (cur_width >= 700) {
            
            cr.SetSourceRGB(1.0, 1.0, 0.72);
            cr.Rectangle(0, 0, full, 30);
            cr.Clip();
            cr.Paint();
            cr.ResetClip();
            
            cr.SetSourceRGB(1.0, 0.68, 0.68);
            cr.Rectangle(full, 0, till-full, 30);    
            cr.Clip();
            cr.Paint();
            cr.ResetClip();

        } else { 
             
            cr.SetSourceRGB(1.0, 1.0, 0.72);
            cr.Rectangle(0, 0, till, 30);
            cr.Clip();
            cr.Paint();
            cr.ResetClip();
       }  

       cr.SetSourceRGB(0.35, 0.31, 0.24);
       
       for (int i=1; i<=num.Length; i++) {
           
           cr.MoveTo(i*step, 0);
           cr.LineTo(i*step, 5);    
           cr.Stroke();
           
           TextExtents extents = cr.TextExtents(num[i-1]);
           cr.MoveTo(i*step-extents.Width/2, 15);
           cr.TextPath(num[i-1]);
           cr.Stroke();
       }
        
        ((IDisposable) cr.Target).Dispose();                                      
        ((IDisposable) cr).Dispose();

        return true;
    }
}


class SharpApp : Window {
 
    int cur_value = 0;
    Burning burning;
    
    public SharpApp() : base("Burning")
    {
        SetDefaultSize(350, 200);
        SetPosition(WindowPosition.Center);
        DeleteEvent += delegate { Application.Quit(); };
       
        VBox vbox = new VBox(false, 2);
        
        HScale scale = new HScale(0, 750, 1);
        scale.SetSizeRequest(160, 35);
        scale.ValueChanged += OnChanged;
        
        Fixed fix = new Fixed();
        fix.Put(scale, 50, 50);
        
        vbox.PackStart(fix);
        
        burning = new Burning();
        vbox.PackStart(burning, false, false, 0);

        Add(vbox);

        ShowAll();
    }
    
    void OnChanged(object sender, EventArgs args)
    {
        Scale scale = (Scale) sender;
        cur_value = (int) scale.Value;
        burning.QueueDraw();
    }
    
    public int CurValue {
        get { return cur_value; }
    }


    public static void Main()
    {
        Application.Init();
        new SharpApp();
        Application.Run();
    }
}

我们将一个DrawingArea放在窗口的底部,并手动绘制整个小部件。所有重要的代码都位于 Burning 类的 OnExposeEvent 方法中。此小部件以图形方式显示介质的总容量和可供我们使用的可用空间。该小部件由一个 scale 小部件控制。我们自定义小部件的最小值为 0,最大值为 750。如果我们达到 700 的值,我们开始用红色绘制。 这通常表示过度刻录。

string[] num = new string[] { "75", "150", "225", "300", 
    "375", "450", "525", "600", "675" };

这些数字显示在刻录小部件上。它们显示了介质的容量。

SharpApp parent = (SharpApp) GetAncestor (Gtk.Window.GType);        
int cur_width = parent.CurValue;

这两行从 scale 小部件获取当前数字。我们获取父小部件,并从父小部件获取当前值。

int till = (int) ((width / 750.0) * cur_width);
int full = (int) ((width / 750.0) * 700);

till 参数确定要绘制的总大小。 此值来自滑块小部件。它是整个区域的一部分。 full 参数确定我们开始用红色绘制的点。

cr.SetSourceRGB(1.0, 1.0, 0.72);
cr.Rectangle(0, 0, full, 30);
cr.Clip();
cr.Paint();
cr.ResetClip();

这里的这段代码绘制一个黄色矩形,直到介质已满。

TextExtents extents = cr.TextExtents(num[i-1]);
cr.MoveTo(i*step-extents.Width/2, 15);
cr.TextPath(num[i-1]);
cr.Stroke();

这里的这段代码在刻录小部件上绘制数字。我们计算 TextExtents 以正确地定位文本。

void OnChanged(object sender, EventArgs args)
{
    Scale scale = (Scale) sender;
    cur_value = (int) scale.Value;
    burning.QueueDraw();
}

我们从 scale 小部件获取值,将其存储在 cur_value 变量中供以后使用。我们重新绘制刻录小部件。

Burning widget
图:刻录小部件

在本章中,我们在 GTK# 中创建了一个自定义小部件。