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

Win 10
C# C++ (VS 2017/19), (Java, PHP)
BeitragVerfasst: Do 08.07.21 14:19 
Anmerkung: Ich weiß nicht ob die Frage hier rein gehört oder in den Tab Programmierwerkzeuge

Ich habe eine Form-Klasse erstellt, die ich als Basisklasse für alle meine borderless Forms verwende. Diese Klasse ermöglicht es mir, die Größe des Fensters zu ändern, ohne eine Border zu haben. Diese Klasse verwendet die Nachricht WM_NCHITEST der WndProc. Das Problem ist, dass alle im Formular platzierten Controls im Designer nicht per Klick auswählbar sind. Ich kann Container wie Panels auswählen, indem ich einen Bereich darin markiere, aber wenn ich darauf klicke, wähle ich das Formular erneut aus. Das bedeutet auch, dass ich keine Controls durch Ziehen platzieren kann.

Dies ist die Klasse BorderlessResizeableForm:

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:
218:
219:
220:
221:
222:
223:
224:
225:
226:
227:
228:
229:
230:
231:
232:
233:
234:
235:
236:
237:
238:
239:
240:
241:
242:
243:
244:
245:
246:
247:
248:
249:
250:
251:
252:
253:
254:
255:
256:
257:
258:
259:
260:
261:
262:
263:
264:
265:
266:
267:
268:
269:
270:
271:
272:
273:
274:
275:
276:
277:
278:
279:
280:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;

namespace CustomWinFormsControls {
    /// <summary>
    /// Form with a <see cref="FormBorderStyle.None"/> which can be resized and moved if drag control is set.
    /// </summary>
    public class BorderlessResizableForm : Form {
        private FormDragControl dragControl;
        private Dictionary<Control, BorderWndProcFilter> borderFilters;

        private int _resizeBorderThickness = 5;

        /// <summary>
        /// The control which should be used to move the window around.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Browsable(true)]
        [DefaultValue(null)]
        [Category("Behaviour")]
        [Description("The control which should be used to move the window around.")]
        public Control DragTarget {
            get => this.dragControl?.Target;
            set {
                if (!(this.dragControl is null))
                    this.dragControl.Target = value;
            }
        }

        /// <summary>
        /// The thickness of the resize border on all sides of the window. The border is invisible. Setting it will not result in a padding.
        /// </summary>
        [EditorBrowsable(EditorBrowsableState.Always)]
        [Browsable(true)]
        [DefaultValue(null)]
        [Category("Behaviour")]
        [Description("The thickness of the resize border on all sides of the window. The border is invisible. Setting it will not result in a padding.")]
        public int ResizeBorderThickness {
            get => this._resizeBorderThickness;
            set {
                if (this._resizeBorderThickness == value)
                    return;

                this._resizeBorderThickness = value;

                foreach (BorderWndProcFilter filter in this.borderFilters.Values)
                    filter.BorderThinckness = value;
            }
        }

        /// <summary>
        /// Creates a new instance of the class <see cref="BorderlessResizableForm"/>
        /// </summary>
        public BorderlessResizableForm() {
            this.FormBorderStyle = FormBorderStyle.None;
            this.SetStyle(ControlStyles.ResizeRedraw, true);

            this.dragControl = new FormDragControl();
            this.borderFilters = new Dictionary<Control, BorderWndProcFilter>();
            this.ControlAdded += this.Control_ControlAdded;
            this.ControlRemoved += this.Control_ControlRemoved;
        }

        private void Control_ControlAdded(object sender, ControlEventArgs e) {
            this.ApplyFiltersRecursive(this, e.Control);
        }

        private void Control_ControlRemoved(object sender, ControlEventArgs e) {
            this.RemoveFiltersRecursive(e.Control);
        }

        private void ApplyFiltersRecursive(Control parent, Control control) {
            if (control is null)
                return;

            this.borderFilters[control] = new BorderWndProcFilter(parent, control, this.ResizeBorderThickness);

            // remove them in case they were already added.
            control.ControlAdded -= this.Control_ControlAdded;
            control.ControlRemoved -= this.Control_ControlRemoved;

            control.ControlAdded += this.Control_ControlAdded;
            control.ControlRemoved += this.Control_ControlRemoved;

            foreach (Control c in control.Controls)
                if (!(c is null))
                    this.ApplyFiltersRecursive(parent, c);
        }

        private void RemoveFiltersRecursive(Control control) {
            if (control is null)
                return;

            this.borderFilters.Remove(control);
            control.ControlAdded -= this.Control_ControlAdded;
            control.ControlRemoved -= this.Control_ControlRemoved;

            foreach (Control c in control.Controls)
                if (!(c is null))
                    this.RemoveFiltersRecursive(c);
        }

        protected override void WndProc(ref Message m) {
            if (m.Msg != BorderWndProcFilter.WM_NCHITTEST) {
                base.WndProc(ref m);
                return;
            }

            Point pos = this.PointToClient(new Point(m.LParam.ToInt32()));

            // if in top left corner
            if (pos.X <= this.ResizeBorderThickness && pos.Y <= this.ResizeBorderThickness) {
                m.Result = new IntPtr(BorderWndProcFilter.HTTOPLEFT);
                return;
            }

            // if in top right corner
            if (pos.X >= this.ClientSize.Width - this.ResizeBorderThickness && pos.Y <= this.ResizeBorderThickness) {
                m.Result = new IntPtr(BorderWndProcFilter.HTTOPRIGHT);
                return;
            }

            // if in bottom left corner
            if (pos.X <= this.ResizeBorderThickness && pos.Y >= this.ClientSize.Height - this.ResizeBorderThickness) {
                m.Result = new IntPtr(BorderWndProcFilter.HTBOTTOMLEFT);
                return;
            }

            // if in bottom right corner
            if (pos.X >= this.ClientSize.Width - this.ResizeBorderThickness && pos.Y >= this.ClientSize.Height - this.ResizeBorderThickness) {
                m.Result = new IntPtr(BorderWndProcFilter.HTBOTTOMRIGHT);
                return;
            }

            // if on the left
            if (pos.X <= this.ResizeBorderThickness) {
                m.Result = new IntPtr(BorderWndProcFilter.HTLEFT);
                return;
            }

            // if on top
            if (pos.Y <= this.ResizeBorderThickness) {
                m.Result = new IntPtr(BorderWndProcFilter.HTTOP);
                return;
            }

            // if on the right
            if (pos.X >= this.ClientSize.Width - this.ResizeBorderThickness) {
                m.Result = new IntPtr(BorderWndProcFilter.HTRIGHT);
                return;
            }

            // if on the bottom
            if (pos.Y >= this.ClientSize.Height - this.ResizeBorderThickness) {
                m.Result = new IntPtr(BorderWndProcFilter.HTBOTTOM);
                return;
            }

            base.WndProc(ref m);
        }
    }

    /// <summary>
    /// A filter for the WM_NCHITTEST message to ignore it when the mouse hovers the resize border.
    /// </summary>
    public class BorderWndProcFilter : NativeWindow {
        public const int WM_NCHITTEST = 0x0084;

        public const int HTCAPTION = 0x00000002;
        public const int HTTOP = 0x0000000C;
        public const int HTBOTTOM = 0x0000000F;
        public const int HTLEFT = 0x0000000A;
        public const int HTRIGHT = 0x0000000B;
        public const int HTTOPLEFT = 0x0000000D;
        public const int HTTOPRIGHT = 0x0000000E;
        public const int HTBOTTOMLEFT = 0x00000010;
        public const int HTBOTTOMRIGHT = 0x00000011;
        public const int HTTRANSPARENT = -1;

        public static List<Type> TypeBlacklist { get; set; } = new List<Type>();

        private Control parent;
        private Control child;

        public int BorderThinckness { get; set; }
        public bool ResizeBorderLeft { get; set; }
        public bool ResizeBorderRight { get; set; }
        public bool ResizeBorderTop { get; set; }
        public bool ResizeBorderBottom { get; set; }

        /// <summary>
        /// Creates a new instance of the class <see cref="BorderWndProcFilter"/>. Sets all borders to true.
        /// </summary>
        /// <param name="parent">The resizable control which should have a non blocked resize border.</param>
        /// <param name="child">The child control the filter should be applied to for not blocking the resize border of the parent control.</param>
        /// <param name="borderThinckness">The resize border thickness of the parent.</param>
        public BorderWndProcFilter(Control parent, Control child, int borderThinckness) {
            this.parent = parent;
            this.child = child;

            try {
                if (!BorderWndProcFilter.TypeBlacklist.Contains(child.GetType()))
                    this.AssignHandle(child.Handle);
            } catch (Exception) { }

            this.BorderThinckness = borderThinckness;

            this.ResizeBorderLeft = true;
            this.ResizeBorderRight = true;
            this.ResizeBorderTop = true;
            this.ResizeBorderBottom = true;
        }

        /// <summary>
        /// Creates a new instance of the class <see cref="BorderWndProcFilter"/>. Sets all borders to true.
        /// </summary>
        /// <param name="parent">The resizable control which should have a non blocked resize border.</param>
        /// <param name="child">The child control the filter should be applied to for not blocking the resize border of the parent control.</param>
        /// <param name="borderThinckness">The resize border thickness of the parent.</param>
        /// <param name="left">If true the parent has a left-side resize border which should not be blocked.</param>
        /// <param name="right">If true the parent has a right-side resize border which should not be blocked.</param>
        /// <param name="top">If true the parent has a top-side resize border which should not be blocked.</param>
        /// <param name="bottom">If true the parent has a bottom-side resize border which should not be blocked.</param>
        public BorderWndProcFilter(Control parent, Control child, int borderThinckness, bool left, bool right, bool top, bool bottom) {
            this.parent = parent;
            this.child = child;
            this.AssignHandle(child.Handle);
            this.BorderThinckness = borderThinckness;

            this.ResizeBorderLeft = left;
            this.ResizeBorderRight = right;
            this.ResizeBorderTop = top;
            this.ResizeBorderBottom = bottom;
        }

        protected override void WndProc(ref Message m) {
            if (m.Msg != WM_NCHITTEST) {
                base.WndProc(ref m);
                return;
            }

            if (this.parent is null) {
                base.WndProc(ref m);
                return;
            }

            Point pos = new Point(m.LParam.ToInt32());
            Point parentGlobalPos = this.parent is Form ? this.parent.Location : this.parent.PointToScreen(this.parent.Location);

            // if on the left
            if (pos.X <= parentGlobalPos.X + this.BorderThinckness && this.ResizeBorderLeft) {
                m.Result = new IntPtr(HTTRANSPARENT);
                return;
            }

            // if on top
            if (pos.Y <= parentGlobalPos.Y + this.BorderThinckness && this.ResizeBorderTop) {
                m.Result = new IntPtr(HTTRANSPARENT);
                return;
            }

            // if on the right
            if (pos.X >= parentGlobalPos.X + this.parent.Width - this.BorderThinckness && this.ResizeBorderRight) {
                m.Result = new IntPtr(HTTRANSPARENT);
                return;
            }

            // if on the bottom
            if (pos.Y >= parentGlobalPos.Y + this.parent.Height - this.BorderThinckness && this.ResizeBorderBottom) {
                m.Result = new IntPtr(HTTRANSPARENT);
                return;
            }

            base.WndProc(ref m);
        }
    }
}


Was is ebenfalls versucht habe, ist das standardmäßige Ausführen der WndProc während der design-time. Funktioniert ebenfalls nicht.

ausblenden C#-Quelltext
1:
2:
3:
4:
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime) {
    base.WndProc(ref m);
    return;
}
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4627
Erhaltene Danke: 1013

Win10
C#, C++ (VS 2015/17/19)
BeitragVerfasst: Do 08.07.21 16:37 
Da fallen mir nur 2 Möglichkeiten zum Finden des Problems ein:
1. Eine zweite VS-Instanz öffnen, deine Solution laden + dich an den ersten VS-Prozess hängen und deinen Code debuggen (Haltepunkte vorher passend setzen).
2. Deinen Code stückweise reduzieren (auskommentieren), bis du die fehlerhafte Codestelle gefunden hast.

PS: Die Frage ist hier schon richtig, da es ja um eine von dir erstellte WinForms-Klasse geht.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4641
Erhaltene Danke: 967


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Do 08.07.21 19:54 
Habs mal gerade ausprobiert und konnte kein Problem feststellen. Musste nur die Teile die sich auf FormDragControl beziehen auskommentieren.
Der Designer verhält sich bei mir nicht anders egal ob ich von Form oder dieser BorderlessResizableForm ableite.
Kasko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 122

Win 10
C# C++ (VS 2017/19), (Java, PHP)
BeitragVerfasst: Do 08.07.21 20:38 
Hab das Problem gefunden. Es ist die WndProc des BorderWndProcFilters. Ich weiß jedoch nicht, ob die Position der Message oder die Bounds der Control das Problem sind - ist jedoch nicht von Bedeutung. Zudem ist LicenseManager.UsageMode zum Zeitpunkt des Aufrufes der Filter-WndProc nicht auf Designtime gesetzt, wodurch der Fehler auch nach dem Versuch die Filterung auf die Runtime zu begrenzen auftrat. Ein einfaches Forwarden der protected Property DesignMode über eine weitere internal Property hat das Problem gelöst.

Jedoch ist mir schleierhaft wie es bei dir @Ralf Jansen einwandfrei funktionieren konnte.

Moderiert von user profile iconTh69: C#-Tags hinzugefügt