2014-09-29 25 views
7

Tôi đã cố vẽ đường viền tùy chỉnh cho các điều khiển .Net WinForms hiện có. Tôi đã thử điều này bằng cách tạo một lớp mà từ điều khiển tôi muốn thay đổi màu viền của, và sau đó thử một vài thứ trong khi vẽ. Tôi đã thử các cách sau:Làm cách nào để vẽ đường viền tùy chỉnh trên .Net WinForms kiểm soát

1. Catch WM_NCPAINT. Điều này hoạt động, phần nào. Vấn đề với mã bên dưới là khi điều khiển thay đổi kích thước, đường viền sẽ bị cắt ở bên phải và phía dưới. Không tốt.

protected override void WndProc(ref Message m) 
{ 
    if (m.Msg == NativeMethods.WM_NCPAINT) { 
    WmNcPaint(ref m); 
    return; 
    } 
    base.WndProc(ref m); 
} 

private void WmNcPaint(ref Message m) 
{ 
    if (BorderStyle == BorderStyle.None) { 
    return; 
    } 

    IntPtr hDC = NativeMethods.GetWindowDC(m.HWnd); 
    if (hDC != IntPtr.Zero) { 
    using (Graphics g = Graphics.FromHdc(hDC)) { 
     ControlPaint.DrawBorder(g, new Rectangle(0, 0, this.Width, this.Height), _BorderColor, ButtonBorderStyle.Solid); 
    } 
    m.Result = (IntPtr)1; 
    NativeMethods.ReleaseDC(m.HWnd, hDC); 
    } 
} 

2. Ghi đè void OnPaint. Điều này làm việc cho một số điều khiển, nhưng không phải tất cả. Điều này cũng yêu cầu bạn phải đặt BorderStyle thành BorderStyle.None và bạn phải xóa nền theo cách thủ công trên sơn, nếu không, hãy you get this khi bạn đổi kích thước.

protected override void OnPaint(PaintEventArgs e) 
{ 
    base.OnPaint(e); 
    ControlPaint.DrawBorder(e.Graphics, new Rectangle(0, 0, this.Width, this.Height), _BorderColor, ButtonBorderStyle.Solid); 
} 

3. Overriding void OnResizevoid OnPaint (như trong phương pháp 2). Bằng cách này, nó vẽ tốt với thay đổi kích thước, nhưng không phải khi Bảng điều khiển có kích hoạt AutoScroll, trong trường hợp này nó sẽ look like this khi cuộn xuống. Nếu tôi cố gắng sử dụng WM_NCPAINT để vẽ đường viền, Refresh() sẽ không có hiệu lực.

protected override void OnResize(EventArgs eventargs) 
{ 
    base.OnResize(eventargs); 
    Refresh(); 
} 

Đề xuất được hoan nghênh hơn. Tôi muốn biết những gì tốt nhất cách để đi về việc này là, cho nhiều loại điều khiển (tôi sẽ phải làm điều này cho nhiều điều khiển WinForms mặc định).

+0

tôi chắc chắn rằng bạn đã nghe nói nó trước, nhưng đề nghị chân thành của tôi sẽ được chỉ cần sử dụng WPF thay vì WinFroms. Ngoài ra, bạn có mong muốn của tôi cho may mắn trong việc này, và +1 của tôi cho một câu hỏi tốt bằng văn bản. – BradleyDotNET

+0

Cảm ơn bạn! Và có, tôi đã nghe nói rằng trước đây, nhiều lần :) Tôi vẫn cần phải tìm thời gian để tìm hiểu WPF, nhưng dự án này là quá sâu vào WinForms để chuyển đổi nó thành WPF. Có lẽ trong tương lai. – Codecat

+0

void override void OnResize (EventArgs eventargs) { base.OnResize (eventargs); Làm mới(); } – houssam

Trả lời

1

EDIT: Vì vậy, tôi đã tìm ra những gì đã gây ra vấn đề ban đầu của tôi. Sau một thời gian rất dài của việc sửa đổi, thử nghiệm và xem xét mã nguồn khung .Net, đây là một cách dứt khoát để làm điều đó (xem xét bạn có quyền kiểm soát kế thừa từ điều khiển bạn muốn vẽ đường viền tùy chỉnh):

[DllImport("user32.dll")] 
public static extern bool RedrawWindow(IntPtr hWnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, RedrawWindowFlags flags); 

[Flags()] 
public enum RedrawWindowFlags : uint 
{ 
    Invalidate = 0X1, 
    InternalPaint = 0X2, 
    Erase = 0X4, 
    Validate = 0X8, 
    NoInternalPaint = 0X10, 
    NoErase = 0X20, 
    NoChildren = 0X40, 
    AllChildren = 0X80, 
    UpdateNow = 0X100, 
    EraseNow = 0X200, 
    Frame = 0X400, 
    NoFrame = 0X800 
} 

// Make sure that WS_BORDER is a style, otherwise borders aren't painted at all 
protected override CreateParams CreateParams 
{ 
    get 
    { 
    if (DesignMode) { 
     return base.CreateParams; 
    } 
    CreateParams cp = base.CreateParams; 
    cp.ExStyle &= (~0x00000200); // WS_EX_CLIENTEDGE 
    cp.Style |= 0x00800000; // WS_BORDER 
    return cp; 
    } 
} 

// During OnResize, call RedrawWindow with Frame|UpdateNow|Invalidate so that the frame is always redrawn accordingly 
protected override void OnResize(EventArgs e) 
{ 
    base.OnResize(e); 
    if (DesignMode) { 
    RecreateHandle(); 
    } 
    RedrawWindow(this.Handle, IntPtr.Zero, IntPtr.Zero, RedrawWindowFlags.Frame | RedrawWindowFlags.UpdateNow | RedrawWindowFlags.Invalidate); 
} 

// Catch WM_NCPAINT for painting 
protected override void WndProc(ref Message m) 
{ 
    if (m.Msg == NativeMethods.WM_NCPAINT) { 
    WmNcPaint(ref m); 
    return; 
    } 
    base.WndProc(ref m); 
} 

// Paint the custom frame here 
private void WmNcPaint(ref Message m) 
{ 
    if (BorderStyle == BorderStyle.None) { 
    return; 
    } 

    IntPtr hDC = NativeMethods.GetWindowDC(m.HWnd); 
    using (Graphics g = Graphics.FromHdc(hDC)) { 
    g.DrawRectangle(new Pen(_BorderColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); 
    } 
    NativeMethods.ReleaseDC(m.HWnd, hDC); 
} 

vì vậy, trong Tóm lại, để lại OnPaint như là, đảm bảo WS_BORDER được thiết lập, sau đó bắt WM_NCPAINT và vẽ biên giới thông qua HDC, và chắc chắn rằng RedrawWindow được gọi trong OnResize.

Điều này thậm chí có thể được mở rộng để vẽ thanh cuộn tùy chỉnh, vì đó là một phần của khung cửa sổ mà bạn có thể vẽ trong thời gian WM_NCPAINT.

Tôi đã xóa câu trả lời cũ của mình khỏi điều này.

EDIT 2: Đối ComboBox, bạn phải bắt WM_PAINT trong WndProc(), bởi vì đối với một số lý do nguồn Net cho sơn ComboBox không sử dụng OnPaint(), nhưng WM_PAINT. Vì vậy, một cái gì đó như thế này:

protected override void WndProc(ref Message m) 
{ 
    base.WndProc(ref m); 

    if (m.Msg == NativeMethods.WM_PAINT) { 
    OnWmPaint(); 
    } 
} 

private void OnWmPaint() 
{ 
    using (Graphics g = CreateGraphics()) { 
    if (!_HasBorders) { 
     g.DrawRectangle(new Pen(BackColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); 
     return; 
    } 
    if (!Enabled) { 
     g.DrawRectangle(new Pen(_BorderColorDisabled), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); 
     return; 
    } 
    if (ContainsFocus) { 
     g.DrawRectangle(new Pen(_BorderColorActive), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); 
     return; 
    } 
    g.DrawRectangle(new Pen(_BorderColor), new Rectangle(0, 0, this.Width - 1, this.Height - 1)); 
    } 
} 
-2

Thực ra bạn có thể sử dụng các điều khiển khả năng tương tác WPF để tạo bất kỳ đường viền nào bạn muốn.

  1. Tạo Mẫu
  2. Nơi ElementHost kiểm soát (từ WPF Interoperability) về hình thức
  3. Tạo một WPF User Control (hoặc sử dụng bảng điều khiển hiện có) với biên giới tùy chỉnh
  4. Nơi WindowsFormsHost kiểm soát bên trong WPF User Control (kiểm soát này sẽ được sử dụng sau này để lưu trữ kiểm soát của bạn)
  5. Set thuộc tính với WPF User Control từ bước trước ElementHost Child

    Tôi đồng ý rằng giải pháp của tôi chứa rất nhiều điều khiển lồng nhau, nhưng từ quan điểm của tôi nó làm giảm đáng kể số lượng các vấn đề liên quan đến OnPaint nested controls WPF+WinForm

+2

Điều này có vẻ quá mã nguồn lực, cộng với nó quá phức tạp, như bạn đã nói. – Codecat

+0

Tài nguyên chuyên sâu? Nó phụ thuộc vào số lượng điều khiển bạn muốn vẽ đường viền tùy chỉnh. – tarasn

+0

Mọi điều khiển đơn có đường viền. Câu trả lời của bạn là một giải pháp khả thi, nhưng nó không lý tưởng. – Codecat

Các vấn đề liên quan