原文: http://zetcode.com/gui/csharpqyoto/customwidget/

在 Qyoto C# 编程教程的这一部分中,我们将展示如何创建自定义窗口小部件。

大多数工具包通常仅提供最常用的窗口小部件,例如按钮,文本窗口小部件,滑块等。没有工具箱可以提供所有可能的窗口小部件。 程序员必须自己创建此类小部件。 他们使用工具箱提供的绘图工具来完成此任务。 有两种可能性。 程序员可以修改或增强现有的小部件。 或者,他可以从头开始创建自定义窗口小部件。

在本章中,我们将创建两个自定义窗口小部件。 刻录小部件和 Led 小部件。

刻录小部件

在下一个示例中,我们将创建一个自定义刻录小部件。 可以在 Nero 或 K3B 之类的应用中看到此小部件。 该小部件将从头开始创建。

burning.cs

  1. using System;
  2. using QtCore;
  3. using QtGui;
  4. public class Burning : QWidget
  5. {
  6. const int PANEL_HEIGHT = 30;
  7. const int DISTANCE = 19;
  8. const int LINE_WIDTH = 5;
  9. const int DIVISIONS = 10;
  10. const float FULL_CAPACITY = 700f;
  11. const float MAX_CAPACITY = 750f;
  12. QColor redColor = new QColor(255, 175, 175);
  13. QColor yellowColor = new QColor(255, 255, 184);
  14. QWidget parent;
  15. String[] num =
  16. {
  17. "75", "150", "225", "300",
  18. "375", "450", "525", "600",
  19. "675"
  20. };
  21. public Burning(QWidget parent)
  22. {
  23. this.parent = parent;
  24. MinimumHeight = PANEL_HEIGHT;
  25. }
  26. protected override void OnPaintEvent(QPaintEvent pe)
  27. {
  28. QPainter ptr = new QPainter(this);
  29. DrawWidget(ptr);
  30. ptr.End();
  31. }
  32. void DrawWidget(QPainter ptr)
  33. {
  34. QyotoApp burn = (QyotoApp) parent;
  35. float slid_width = burn.GetCurrentWidth();
  36. float width = Size.Width;
  37. float step = width / DIVISIONS;
  38. float till = (width / MAX_CAPACITY) * slid_width;
  39. float full = (width / MAX_CAPACITY) * FULL_CAPACITY;
  40. if (slid_width > FULL_CAPACITY)
  41. {
  42. ptr.Pen = yellowColor;
  43. ptr.Brush = yellowColor;
  44. ptr.DrawRect(new QRectF(0, 0, full, PANEL_HEIGHT));
  45. ptr.Pen = redColor;
  46. ptr.Brush = redColor;
  47. ptr.DrawRect(new QRectF(full+1, 0, till-full, PANEL_HEIGHT));
  48. } else
  49. {
  50. if (slid_width > 0)
  51. {
  52. ptr.Pen = yellowColor;
  53. ptr.Brush = yellowColor;
  54. ptr.DrawRect(new QRectF(0, 0, till, PANEL_HEIGHT));
  55. }
  56. }
  57. ptr.Pen = new QColor(90, 90, 90);
  58. ptr.SetBrush(BrushStyle.NoBrush);
  59. ptr.DrawRect(0, 0, Size.Width-1, PANEL_HEIGHT-1);
  60. QFont newFont = ptr.Font;
  61. newFont.PointSize = 7;
  62. ptr.Font = newFont;
  63. QFontMetrics metrics = new QFontMetrics(newFont);
  64. for (int i = 1; i <= num.Length; i++)
  65. {
  66. ptr.DrawLine(new QLineF(i*step, 1, i*step, LINE_WIDTH));
  67. int w = metrics.Width(num[i-1]);
  68. ptr.DrawText(new QPointF(i*step-w/2, DISTANCE), num[i-1]);
  69. }
  70. }
  71. }

在这个文件中,我们创建了刻录小部件。

  1. public class Burning : QWidget
  2. {
  3. ...

自定义窗口小部件基于QWidget类。

  1. const int PANEL_HEIGHT = 30;
  2. const int DISTANCE = 19;
  3. const int LINE_WIDTH = 5;
  4. const int DIVISIONS = 10;
  5. const float FULL_CAPACITY = 700f;
  6. const float MAX_CAPACITY = 750f;

这些是重要的常数。 PANEL_HEIGHT定义自定义窗口小部件的高度。 DISTANCE是比例尺上的数字与其父边框顶部之间的距离。 LINE_WIDTH是垂直线的宽度。 DIVISIONS是秤的数量。 FULL_CAPACITY是媒体的容量。 达到目标后,就会发生过度刻录。 这通过红色可视化。 MAX_CAPACITY是介质的最大容量。

  1. String[] num =
  2. {
  3. "75", "150", "225", "300",
  4. "375", "450", "525", "600",
  5. "675"
  6. };

我们使用这些数字来构建刻录小部件的比例。

  1. protected override void OnPaintEvent(QPaintEvent pe)
  2. {
  3. QPainter ptr = new QPainter(this);
  4. DrawWidget(ptr);
  5. ptr.End();
  6. }

自定义窗口小部件的图形委托给DrawWidget()方法。

  1. QyotoApp burn = (QyotoApp) parent;

我们检索对父窗口小部件的引用。

  1. float slid_width = burn.GetCurrentWidth();

我们使用它来获取当前选定的滑块值。

  1. float width = Size.Width;

我们得到小部件的宽度。 自定义窗口小部件的宽度是动态的。 用户可以调整大小。

  1. float till = (width / MAX_CAPACITY) * slid_width;
  2. float full = (width / MAX_CAPACITY) * FULL_CAPACITY;

我们使用width变量进行转换。 在比例尺值和自定义小部件的度量之间。 请注意,我们使用浮点值。 我们在绘图中获得了更高的精度。

  1. ptr.Pen = redColor;
  2. ptr.Brush = redColor;
  3. ptr.DrawRect(new QRectF(full+1, 0, till-full, PANEL_HEIGHT));

这三行画出红色矩形,表示过度燃烧。

  1. ptr.DrawRect(0, 0, Size.Width-1, PANEL_HEIGHT-1);

这是小部件的周长。 外部矩形。

  1. ptr.DrawLine(new QLineF(i*step, 1, i*step, LINE_WIDTH));

在这里,我们画出小的垂直线。

  1. QFontMetrics metrics = new QFontMetrics(newFont);
  2. ...
  3. int w = metrics.Width(num[i-1]);
  4. ptr.DrawText(new QPointF(i*step-w/2, DISTANCE), num[i-1]);

在这里,我们在比例尺上绘制数字。 为了精确定位数字,我们必须获得字符串的宽度。

main.cs

  1. using System;
  2. using QtCore;
  3. using QtGui;
  4. /**
  5. * ZetCode Qyoto C# tutorial
  6. *
  7. * In this program, we create
  8. * a custom Burning widget.
  9. *
  10. * @author Jan Bodnar
  11. * website zetcode.com
  12. * last modified October 2012
  13. */
  14. public class QyotoApp : QWidget
  15. {
  16. const int MAX_CAPACITY = 750;
  17. QSlider slider;
  18. QWidget widget;
  19. int cur_width;
  20. public QyotoApp()
  21. {
  22. WindowTitle = "The Burning Widget";
  23. InitUI();
  24. Resize(370, 200);
  25. Move(300, 300);
  26. Show();
  27. }
  28. void InitUI()
  29. {
  30. slider = new QSlider(Qt.Orientation.Horizontal , this);
  31. slider.Maximum = MAX_CAPACITY;
  32. slider.SetGeometry(50, 50, 130, 30);
  33. slider.ValueChanged += OnValueChanged;
  34. QVBoxLayout vbox = new QVBoxLayout(this);
  35. QHBoxLayout hbox = new QHBoxLayout();
  36. vbox.AddStretch(1);
  37. widget = new Burning(this);
  38. hbox.AddWidget(widget, 0);
  39. vbox.AddLayout(hbox);
  40. Layout = vbox;
  41. }
  42. [Q_SLOT]
  43. void OnValueChanged(int val)
  44. {
  45. cur_width = val;
  46. widget.Repaint();
  47. }
  48. public int GetCurrentWidth()
  49. {
  50. return cur_width;
  51. }
  52. [STAThread]
  53. public static int Main(String[] args)
  54. {
  55. new QApplication(args);
  56. new QyotoApp();
  57. return QApplication.Exec();
  58. }
  59. }

这是主文件。 在这里,我们创建滑块小部件并使用我们的自定义小部件。

  1. widget = new Burning(this);
  2. hbox.AddWidget(widget, 0);

我们创建了刻录小部件的实例,并将其添加到水平框中。

  1. [Q_SLOT]
  2. void OnValueChanged(int val)
  3. {
  4. cur_width = val;
  5. widget.Repaint();
  6. }

当滑块的值更改时,我们将其存储在cur_width变量中,然后重新绘制自定义窗口小部件。

  1. public int GetCurrentWidth()
  2. {
  3. return cur_width;
  4. }

定制小部件调用此方法以获取实际的滑块值。

Qyoto 中的自定义小部件 - 图1

图:刻录小部件

Led 小部件

Led 小部件是一个灯泡,可以将其设置为不同的颜色。 在我们的例子中是红色,绿色,橙色和黑色。 该自定义窗口小部件仅使用 SVG 图像创建。 有四个 SVG 图像。 每个用于 Led 小部件的一种状态。

要编译该示例,我们需要引用qyoto-qtsvg.dll库。 例如,在我们的系统上,我们将-r/usr/local/lib/mono/qyoto/qyoto-qtsvg.dll添加到了编译选项。

led.cs

  1. using System;
  2. using QtCore;
  3. using QtGui;
  4. using QtSvg;
  5. public class Led : QWidget
  6. {
  7. string[] cols;
  8. int col;
  9. public Led(QWidget parent)
  10. {
  11. const int GREEN = 1;
  12. col = GREEN;
  13. SetMinimumSize(50, 50);
  14. SetMaximumSize(50, 50);
  15. cols = new string[] { "red.svg", "green.svg",
  16. "orange.svg", "black.svg" };
  17. }
  18. public void SetColour(int newColour)
  19. {
  20. col = newColour;
  21. Update();
  22. }
  23. protected override void OnPaintEvent(QPaintEvent e)
  24. {
  25. QPainter ptr = new QPainter(this);
  26. DrawCustomWidget(ptr);
  27. ptr.End();
  28. }
  29. void DrawCustomWidget(QPainter ptr)
  30. {
  31. QSvgRenderer srnd = new QSvgRenderer();
  32. srnd.Load(cols[col]);
  33. srnd.Render(ptr);
  34. }
  35. }

led.cs类中,我们构建了自定义窗口小部件。

  1. const int GREEN = 1;
  2. col = GREEN;

当一切正常时,GREEN常量指示 Led 小部件的状态。 颜色变量定义窗口小部件的当前状态。

  1. SetMinimumSize(50, 50);
  2. SetMaximumSize(50, 50);

这两条线强制窗口小部件具有恒定的大小。 SVG 图像的大小为50x50

  1. cols = new string[] { "red.svg", "green.svg",
  2. "orange.svg", "black.svg" };

我们将 SVG 图像文件名存储在cols数组中。

  1. public void SetColour(int newColour)
  2. {
  3. col = newColour;
  4. Update();
  5. }

SetColour()方法将col变量设置为新值。 我们调用Update()方法重绘 Led 小部件以反映新状态。

  1. void DrawCustomWidget(QPainter ptr)
  2. {
  3. QSvgRenderer srnd = new QSvgRenderer();
  4. srnd.Load(cols[col]);
  5. srnd.Render(ptr);
  6. }

DrawCustomWidget()方法中,我们使用QSvgRenderer类显示 SVG 图像。

main.cs

  1. using Qyoto;
  2. using QtCore;
  3. using QtGui;
  4. /**
  5. * ZetCode Qyoto C# tutorial
  6. *
  7. * This program creates a custom Led
  8. * widget.
  9. *
  10. * @author Jan Bodnar
  11. * website zetcode.com
  12. * last modified October 2012
  13. */
  14. public class QyotoApp : QWidget
  15. {
  16. const int RED = 0;
  17. const int GREEN = 1;
  18. const int ORANGE = 2;
  19. const int BLACK = 3;
  20. Led led;
  21. public QyotoApp()
  22. {
  23. WindowTitle = "Led widget";
  24. SetupUI();
  25. Resize(250, 150);
  26. Move(300, 300);
  27. Show();
  28. }
  29. public void SetupUI()
  30. {
  31. QVBoxLayout vbox = new QVBoxLayout();
  32. QHBoxLayout hbox = new QHBoxLayout();
  33. led = new Led(this);
  34. hbox.AddWidget(led);
  35. vbox.AddStretch(1);
  36. vbox.AddLayout(hbox);
  37. vbox.AddStretch(1);
  38. QHBoxLayout hbox2 = new QHBoxLayout();
  39. QPushButton pb1 = new QPushButton("Normal", this);
  40. QPushButton pb2 = new QPushButton("Warning", this);
  41. QPushButton pb3 = new QPushButton("Emergency", this);
  42. QPushButton pb4 = new QPushButton("Off", this);
  43. hbox2.AddWidget(pb1);
  44. hbox2.AddWidget(pb2);
  45. hbox2.AddWidget(pb3);
  46. hbox2.AddWidget(pb4);
  47. vbox.AddLayout(hbox2);
  48. Connect(pb1, SIGNAL("clicked()"), this, SLOT("OnClicked()"));
  49. Connect(pb2, SIGNAL("clicked()"), this, SLOT("OnClicked()"));
  50. Connect(pb3, SIGNAL("clicked()"), this, SLOT("OnClicked()"));
  51. Connect(pb4, SIGNAL("clicked()"), this, SLOT("OnClicked()"));
  52. Layout = vbox;
  53. }
  54. [Q_SLOT]
  55. public void OnClicked()
  56. {
  57. QPushButton sender = (QPushButton) this.Sender();
  58. string text = sender.Text;
  59. if (text == "Normal")
  60. {
  61. led.SetColour(GREEN);
  62. }
  63. else if (text == "Warning")
  64. {
  65. led.SetColour(ORANGE);
  66. }
  67. else if (text == "Emergency")
  68. {
  69. led.SetColour(RED);
  70. }
  71. else if (text == "Off")
  72. {
  73. led.SetColour(BLACK);
  74. }
  75. }
  76. [STAThread]
  77. public static int Main(String[] args)
  78. {
  79. new QApplication(args);
  80. new QyotoApp();
  81. return QApplication.Exec();
  82. }
  83. }

这是main.cs文件。 我们连续有四个按钮,窗口中央有一个 Led 小部件。 这四个按钮控制 Led 小部件的状态。 Led 小部件有四个状态。 正常,警告,紧急和关闭。

  1. led = new Led(this);
  2. hbox.AddWidget(led);

我们创建 Led 小部件的实例并将其放入水平框中。

  1. else if ( text == "Warning")
  2. {
  3. led.SetColour(ORANGE);
  4. }

如果单击警告按钮,则 LED 小部件的颜色将变为橙色。 更准确地说,将加载并显示一个新的橙色 SVG 图像。

Qyoto 中的自定义小部件 - 图2

图:Led 小部件显示“关闭”状态

在 Qyoto C# 教程的这一部分中,我们演示了如何创建自定义窗口小部件。