Autor Beitrag
Kasko
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 106

Win 10
C# C++ (VS 2017/19), (Java, PHP)
BeitragVerfasst: Mo 12.10.20 16:21 
Ich habe eine Klasse erstellt, die einen Standard Windows Close/Min/Max Button darstellt und genutzt werden kann wenn man einen Custom-Header in einem borderless Window mit Windows Default Buttons haben möchte:

Basis-Klasse:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
/// <summary>
/// Modified button which has no focus rectangles when the form which contains this button loses fucus while the button was focused.
/// </summary>
[ToolboxItem(true)]
public class NoFocusCueBotton : Button {
  protected override bool ShowFocusCues => false;

  /// <summary>
  /// Creates a new instance of a <see cref="NoFocusCueBotton"/>
  /// </summary>
  public NoFocusCueBotton() { }

  public override void NotifyDefault(bool value) {
    base.NotifyDefault(false);
  }
}


Windows Button:

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
157:
158:
159:
160:
161:
162:
163:
164:
165:
166:
167:
168:
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180:
181:
182:
183:
184:
185:
186:
187:
188:
189:
190:
191:
192:
193:
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
/// <summary>
/// Button which represents the default close, minimize or maximize buttons of the windows 10 aero theme.
/// </summary>
[ToolboxItem(true)]
public class WindowsDefaultTitleBarButton : NoFocusCueBotton {
  public enum Type {
    Close,
    Maximize,
    Minimize
  }

  /// <summary>
  /// The type which defines the buttons behaviour.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Always)]
  [Browsable(true)]
  [DefaultValue(Type.Close)]
  [Category("Appearance")]
  [Description("The type which defines the buttons behaviour.")]
  public Type ButtonType { get; set; }

  /// <summary>
  /// The background color of the button when the mouse is inside the buttons bounds.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Always)]
  [Browsable(true)]
  [DefaultValue(null)]
  [Category("Appearance")]
  [Description("The background color of the button when the mouse is inside the buttons bounds.")]
  public Color HoverColor { get; set; }

  /// <summary>
  /// The background color of the button when the button is clicked.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Always)]
  [Browsable(true)]
  [DefaultValue(null)]
  [Category("Appearance")]
  [Description("The background color of the button when the button is clicked.")]
  public Color ClickColor { get; set; }

  /// <summary>
  /// The default color of the icon.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Always)]
  [Browsable(true)]
  [DefaultValue(null)]
  [Category("Appearance")]
  [Description("The default color of the icon.")]
  public Color IconColor { get; set; }

  /// <summary>
  /// The color of the icon when the mouse is inside the buttons bounds.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Always)]
  [Browsable(true)]
  [DefaultValue(null)]
  [Category("Appearance")]
  [Description("The color of the icon when the mouse is inside the buttons bounds.")]
  public Color HoverIconColor { get; set; }

  /// <summary>
  /// The color of the icon when the button is clicked.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Always)]
  [Browsable(true)]
  [DefaultValue(null)]
  [Category("Appearance")]
  [Description("The color of the icon when the button is clicked.")]
  public Color ClickIconColor { get; set; }

  /// <summary>
  /// Property which returns the active background color of the button depending on if the button is clicked or hovered.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Never)]
  [Browsable(false)]
  public virtual Color ActiveColor {
    get {
      if (this.Clicked)
        return this.ClickColor;

      if (this.Hovered)
        return this.HoverColor;

      return BackColor;
    }
  }

  /// <summary>
  /// Property which returns the active color of the buttons icon depending on if the button is clicked or hovered.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Never)]
  [Browsable(false)]
  public virtual Color ActiveIconColor {
    get {
      if (this.Clicked)
        return this.ClickIconColor;

      if (this.Hovered)
        return this.HoverIconColor;

      return IconColor;
    }
  }

  /// <summary>
  /// Property which indicates if the mouse is currently inside the bounds of the button.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Never)]
  [Browsable(false)]
  [DefaultValue(false)]
  public bool Hovered { get; set; }

  /// <summary>
  /// Property which indicates if the left mouse button was pressed down inside the buttons bounds. Can be true before the click event is triggered.
  /// </summary>
  [EditorBrowsable(EditorBrowsableState.Never)]
  [Browsable(false)]
  [DefaultValue(false)]
  public bool Clicked { get; set; }

  public WindowsDefaultTitleBarButton() { }

  protected override void OnMouseEnter(EventArgs e) {
    base.OnMouseEnter(e);
    Hovered = true;
  }

  protected override void OnMouseLeave(EventArgs e) {
    base.OnMouseLeave(e);
    Hovered = false;
  }

  protected override void OnMouseDown(MouseEventArgs mevent) {
    base.OnMouseDown(mevent);
    Clicked = true;
  }

  protected override void OnMouseUp(MouseEventArgs mevent) {
    base.OnMouseUp(mevent);
    Clicked = false;
  }

  protected override void OnClick(EventArgs e) {
    if (ButtonType == Type.Close)
      this.FindForm()?.Close();
    else if (ButtonType == Type.Maximize)
      this.FindForm().WindowState = this.FindForm().WindowState == FormWindowState.Maximized ? FormWindowState.Normal : FormWindowState.Maximized;
    else
      this.FindForm().WindowState = FormWindowState.Minimized;

    base.OnClick(e);
  }

  protected override void OnPaint(PaintEventArgs pevent) {
    pevent.Graphics.FillRectangle(new SolidBrush(ActiveColor), pevent.ClipRectangle);
    pevent.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

    if (ButtonType == Type.Close)
      drawCloseIcon(pevent);
    else if (ButtonType == Type.Maximize)
      drawMaximizeIcon(pevent);
    else
      drawMinimizeIcon(pevent);
  }

  protected virtual void drawCloseIcon(PaintEventArgs e) {
    e.Graphics.DrawLine(
      new Pen(new SolidBrush(ActiveIconColor), 1.0f),
      e.ClipRectangle.X + e.ClipRectangle.Width / 2 - 5,
      e.ClipRectangle.Y + e.ClipRectangle.Height / 2 - 5,
      e.ClipRectangle.X + e.ClipRectangle.Width / 2 + 5,
      e.ClipRectangle.Y + e.ClipRectangle.Height / 2 + 5);

    e.Graphics.DrawLine(
      new Pen(new SolidBrush(ActiveIconColor), 1.0f),
      e.ClipRectangle.X + e.ClipRectangle.Width / 2 - 5,
      e.ClipRectangle.Y + e.ClipRectangle.Height / 2 + 5,
      e.ClipRectangle.X + e.ClipRectangle.Width / 2 + 5,
      e.ClipRectangle.Y + e.ClipRectangle.Height / 2 - 5); ;
  }

  protected virtual void drawMaximizeIcon(PaintEventArgs e) {
    if (this.FindForm().WindowState == FormWindowState.Normal) {
      e.Graphics.DrawRectangle(
        new Pen(new SolidBrush(ActiveIconColor), 1.0f),
        new Rectangle(
          e.ClipRectangle.X + e.ClipRectangle.Width / 2 - 5,
          e.ClipRectangle.Y + e.ClipRectangle.Height / 2 - 5,
          1010));
    } else if (this.FindForm().WindowState == FormWindowState.Maximized) {
      e.Graphics.DrawRectangle(
        new Pen(new SolidBrush(ActiveIconColor), 1.0f),
        new Rectangle(
          e.ClipRectangle.X + e.ClipRectangle.Width / 2 - 3,
          e.ClipRectangle.Y + e.ClipRectangle.Height / 2 - 5,
          88));

      Rectangle rect = new Rectangle(
        e.ClipRectangle.X + e.ClipRectangle.Width / 2 - 5,
        e.ClipRectangle.Y + e.ClipRectangle.Height / 2 - 3,
        88);

      e.Graphics.FillRectangle(new SolidBrush(this.ActiveColor), rect);
      e.Graphics.DrawRectangle(new Pen(new SolidBrush(this.ActiveIconColor), 1.0f), rect);
    }
  }

  protected virtual void drawMinimizeIcon(PaintEventArgs e) {
    e.Graphics.DrawLine(
      new Pen(new SolidBrush(ActiveIconColor), 1.0f),
      e.ClipRectangle.X + e.ClipRectangle.Width / 2 - 5,
      e.ClipRectangle.Y + e.ClipRectangle.Height / 2,
      e.ClipRectangle.X + e.ClipRectangle.Width / 2 + 5,
      e.ClipRectangle.Y + e.ClipRectangle.Height / 2);
  }
}


Wenn man jetzt allerdings diese Buttons aus dem Fenster heraus zieht und wieder zurück, dann wird beim "Eintritt" in das Fenster die Zeile, die vor der Bewegung die oberste war erneut gezeichnet. Dadurch entstehen sehr viele schöne Muster :)

PaintBug

Wie kann ich das beheben?

Moderiert von user profile iconNarses: ext. Bild als Anhang hochgeladen.
Einloggen, um Attachments anzusehen!
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4474
Erhaltene Danke: 921


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 12.10.20 19:12 
Du benutzt als Zeichenfläche ClipRectangle. Das wird an den Bildschirmrändern abgeschnitten(entsprechend verkleinert um den nicht sichtbaren Teil). Und da du relativ zu diesem Rectangle zeichnest zeichnest du am Bildschirmrand merkwürdiges Zeug.


Ich würd vorschlagen das Rectangle solltest du irgendwo vordefinieren und dann benutzen so das es von sowas unberührt bleibt.
Das gleiche solltest du für Pens, Brushes etc. tun. Da hängen überall Systemresourcen (Handles) dran. Da du die nach Verwendung nicht disposed verschwendest du diese Resourcen in größerem Maß. Irgendwann wird Windows anfangen rumzuweinen.
Also möglichst alles vordefinieren was konstant oder annähernd konstant ist. Dann wenn nötig ändern wenn sich die von dir veröffentlichten Properties ändern (zum Beispiel deine Color Properties) und in dem Zug an diesen Stellen ein Invalidate aufrufen. Denn dann soll sich diese Änderung auch auf dem Bildschirm sofort wiederspiegeln und nicht erst wenn zufällig irgendwann mal ein neuzeichnen forciert wird.

Und nebenbei. Dir sollte klar sein das du hier gegen das System arbeitest. Deine Lösung hat die Wahrcheinlichkeit auf jedem System das auch nur ein wenig vom Standard abweicht kaput auszusehen.
So Dinge wie Desktopzoom (oder mehrere Monitore mit verschiedenen Zoomstufen), andere Themes/Designs (insbesondese die von Windows Versionen vor 10), Barrierefreiheit etc. Das typische Borderverhalten nachzubauen (warum man das auch immer tun will) ist schwer es gibt da zuviele Varianten und weniger offensichtliche Abhängigkeiten.

Für diesen Beitrag haben gedankt: Kasko
Kasko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 106

Win 10
C# C++ (VS 2017/19), (Java, PHP)
BeitragVerfasst: Mo 12.10.20 21:49 
Okay danke dir. Davon, dass das ClipRectangle abgeschnitten wird wusste ich nichts. Was den Speicher angeht, so habe ich jetzt die Pens, Brushes, etc. immer vorher disposed wenn ich sie neu initialisiere, weil sich die ActiveColor bzw. ActiveIconColor ändert. Allerdings habe ich im Process-Memory-Fenster gesehen, dass der Speicher trotzdem kontinuierlich ansteigt, wenn ich immer wieder über die Buttons hover. Der Aufruf von Dispose vor dem neu initialisieren scheint also nicht zu reichen.

Was die von dir prophezeiten Fehler angeht muss ich sagen, dass ich noch nicht auf die Idee gekommen bin, das diese dadurch ausgelöst werden können. Daraufhin habe ich es ausprobiert und sowohl die Auflösung also auch den Zoom meiner Bildschirme mehrmals geändert (wobei Bildschrim1 != Bildschirm2) und habe das Ergebnis mit den anderen offenen Fenstern verglichen (Visual Studio Project Creation Window, Chrome) und es sieht alles richtig aus und ich konnte keine Fehler finden.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4474
Erhaltene Danke: 921


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Di 13.10.20 09:19 
Wenn das jetzt so ähnlich im Code aussieht wie ich mir das vorstelle sollte in den Paint Events nichts mehr erzeugt werden. Da sollte also eigentlich kein zusätzlicher Speicher belegt werden.
Möglicherweise kommen da irgendein Verhalten der Runtime dazu das nicht ganz offensichtlich ist. Wenn du die Form mal minimierst und wieder maximierst (der Garbabge Collector führt dann ein Collect aus) und sich der Speicherverbrauch wieder normalisiert kannst du das vermutlich ignorieren.