Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. C#
  4. Custom borders

Custom borders

Scheduled Pinned Locked Moved C#
graphicshelptutorialquestion
4 Posts 2 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    Mathew Hall
    wrote on last edited by
    #1

    Hi there. I'm working on a custom control where each side can have a different border thickness. I found this code while googling which allows me to modify the controls non-client area so I can draw my borders:

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
    }

    enum WM_MESSAGE
    {
    WM_NCCALCSIZE = 131,
    WM_NCPAINT = 133,
    WM_NCHITTEST = 132,
    WM_NCLBUTTONDOWN = 161,
    }

    [StructLayout(LayoutKind.Sequential)]
    struct NCCALCSIZE_PARAMS
    {
    public RECT rgrc0, rgrc1, rgrc2;
    public IntPtr lppos;
    }

    [DllImport("User32.dll")]
    private extern static IntPtr GetWindowDC( IntPtr hWnd );

    [DllImport("User32.dll")]
    private extern static int ReleaseDC( IntPtr hWnd, IntPtr hDC );

    private Rectangle _rcButton = Rectangle.Empty;

    protected override void WndProc(ref Message m)
    {
    switch (m.Msg)
    {
    case (int)WM_MESSAGE.WM_NCCALCSIZE :
    if (m.WParam == IntPtr.Zero)
    {
    // shrink the client area
    NCCALCSIZE_PARAMS csp;
    csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
    csp.rgrc0.Left += 1;
    csp.rgrc0.Top += 5;
    csp.rgrc0.Right -= 1;
    csp.rgrc0.Bottom -= 1;
    Marshal.StructureToPtr( csp, m.LParam, false );
    }
    break;

      case (int)WM\_MESSAGE.WM\_NCPAINT :
      {
         // draw the borders in the non client area
         IntPtr hDC = GetWindowDC(m.HWnd);
         Graphics g = Graphics.FromHdc(hDC);
         g.FillRectangle(SystemBrushes.Highlight, 0, 0, this.Width, 5); // top
         g.FillRectangle(SystemBrushes.Highlight, 0, 0, 1, this.Height); // left
         g.FillRectangle(SystemBrushes.Highlight, this.Width-1, 0, 1, this.Height); // right
         g.FillRectangle(SystemBrushes.Highlight, 0, this.Height-1, this.Width, 1); // bottom
         ReleaseDC( m.HWnd, hDC );
         break;
      }
    

    }

    base.WndProc(ref m);
    }

    This works fine until the control is resized - it's client area gets reset. Any ideas on how to fix this? "I think I speak on behalf of everyone here when I say huh?" - Buffy

    J 1 Reply Last reply
    0
    • M Mathew Hall

      Hi there. I'm working on a custom control where each side can have a different border thickness. I found this code while googling which allows me to modify the controls non-client area so I can draw my borders:

      [StructLayout(LayoutKind.Sequential)]
      private struct RECT
      {
      public int Left;
      public int Top;
      public int Right;
      public int Bottom;
      }

      enum WM_MESSAGE
      {
      WM_NCCALCSIZE = 131,
      WM_NCPAINT = 133,
      WM_NCHITTEST = 132,
      WM_NCLBUTTONDOWN = 161,
      }

      [StructLayout(LayoutKind.Sequential)]
      struct NCCALCSIZE_PARAMS
      {
      public RECT rgrc0, rgrc1, rgrc2;
      public IntPtr lppos;
      }

      [DllImport("User32.dll")]
      private extern static IntPtr GetWindowDC( IntPtr hWnd );

      [DllImport("User32.dll")]
      private extern static int ReleaseDC( IntPtr hWnd, IntPtr hDC );

      private Rectangle _rcButton = Rectangle.Empty;

      protected override void WndProc(ref Message m)
      {
      switch (m.Msg)
      {
      case (int)WM_MESSAGE.WM_NCCALCSIZE :
      if (m.WParam == IntPtr.Zero)
      {
      // shrink the client area
      NCCALCSIZE_PARAMS csp;
      csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
      csp.rgrc0.Left += 1;
      csp.rgrc0.Top += 5;
      csp.rgrc0.Right -= 1;
      csp.rgrc0.Bottom -= 1;
      Marshal.StructureToPtr( csp, m.LParam, false );
      }
      break;

        case (int)WM\_MESSAGE.WM\_NCPAINT :
        {
           // draw the borders in the non client area
           IntPtr hDC = GetWindowDC(m.HWnd);
           Graphics g = Graphics.FromHdc(hDC);
           g.FillRectangle(SystemBrushes.Highlight, 0, 0, this.Width, 5); // top
           g.FillRectangle(SystemBrushes.Highlight, 0, 0, 1, this.Height); // left
           g.FillRectangle(SystemBrushes.Highlight, this.Width-1, 0, 1, this.Height); // right
           g.FillRectangle(SystemBrushes.Highlight, 0, this.Height-1, this.Width, 1); // bottom
           ReleaseDC( m.HWnd, hDC );
           break;
        }
      

      }

      base.WndProc(ref m);
      }

      This works fine until the control is resized - it's client area gets reset. Any ideas on how to fix this? "I think I speak on behalf of everyone here when I say huh?" - Buffy

      J Offline
      J Offline
      John Fisher
      wrote on last edited by
      #2

      My guess is that the reason it works until the control is resized is that the client area was never successfully change the the size you are wanting. I took a look at the WM_NCCALCSIZE documentation here[^]. It says that if WParam is FALSE (zero), that a RECT is passed in LParam, not an NCCALCSIZE_PARAMS structure. On the other hand, if WParam is TRUE (non-zero), then you are supposed to get an NCCALCSIZE_PARAMS in LParam. So, it looks like the WM_NCCALCSIZE handling code is never doing anything. I suspect that if you put a breakpoint on the first line of the WM_NCCALCSIZE handling code, then stepped through it, that it would throw an exception at

      csp.rgrc0.Left += 1;

      John
      "You said a whole sentence with no words in it, and I understood you!" -- my wife as she cries about slowly becoming a geek.

      M 1 Reply Last reply
      0
      • J John Fisher

        My guess is that the reason it works until the control is resized is that the client area was never successfully change the the size you are wanting. I took a look at the WM_NCCALCSIZE documentation here[^]. It says that if WParam is FALSE (zero), that a RECT is passed in LParam, not an NCCALCSIZE_PARAMS structure. On the other hand, if WParam is TRUE (non-zero), then you are supposed to get an NCCALCSIZE_PARAMS in LParam. So, it looks like the WM_NCCALCSIZE handling code is never doing anything. I suspect that if you put a breakpoint on the first line of the WM_NCCALCSIZE handling code, then stepped through it, that it would throw an exception at

        csp.rgrc0.Left += 1;

        John
        "You said a whole sentence with no words in it, and I understood you!" -- my wife as she cries about slowly becoming a geek.

        M Offline
        M Offline
        Mathew Hall
        wrote on last edited by
        #3

        Thanks for the tip. I managed to get it to "remember" the resized non-client area by changing the offending case statememt to:

        case (int)WM_MESSAGE.WM_NCCALCSIZE :
        if ( m.WParam == IntPtr.Zero )
        {
        RECT rect = (RECT) Marshal.PtrToStructure(m.LParam, typeof(RECT));
        rect.Left += 1;
        rect.Top += 5;
        rect.Right -= 1;
        rect.Bottom -= 1;
        Marshal.StructureToPtr(rect, m.LParam, false);
        }
        else
        {
        NCCALCSIZE_PARAMS csp;
        csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
        csp.rgrc0.Left += 1;
        csp.rgrc0.Top += 5;
        csp.rgrc0.Right -= 1;
        csp.rgrc0.Bottom -= 1;
        Marshal.StructureToPtr(csp, m.LParam, false);
        }

        Only problem was that sometimes the right and bottom border widths were out by 1. Managed to fix this by overriding OnSizeChanged:

        [DllImport("User32.dll")]
        public static extern int RedrawWindow(IntPtr hwnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, int fuRedraw);

        protected override void OnSizeChanged(EventArgs e)
        {
        base.OnSizeChanged(e);
        // force redraw of the entire control (including non-client area)
        RedrawWindow(this.Handle, IntPtr.Zero, IntPtr.Zero, RDW_FRAME | RDW_UPDATENOW | RDW_INVALIDATE);
        }

        Works like a charm :) "I think I speak on behalf of everyone here when I say huh?" - Buffy

        J 1 Reply Last reply
        0
        • M Mathew Hall

          Thanks for the tip. I managed to get it to "remember" the resized non-client area by changing the offending case statememt to:

          case (int)WM_MESSAGE.WM_NCCALCSIZE :
          if ( m.WParam == IntPtr.Zero )
          {
          RECT rect = (RECT) Marshal.PtrToStructure(m.LParam, typeof(RECT));
          rect.Left += 1;
          rect.Top += 5;
          rect.Right -= 1;
          rect.Bottom -= 1;
          Marshal.StructureToPtr(rect, m.LParam, false);
          }
          else
          {
          NCCALCSIZE_PARAMS csp;
          csp = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
          csp.rgrc0.Left += 1;
          csp.rgrc0.Top += 5;
          csp.rgrc0.Right -= 1;
          csp.rgrc0.Bottom -= 1;
          Marshal.StructureToPtr(csp, m.LParam, false);
          }

          Only problem was that sometimes the right and bottom border widths were out by 1. Managed to fix this by overriding OnSizeChanged:

          [DllImport("User32.dll")]
          public static extern int RedrawWindow(IntPtr hwnd, IntPtr lprcUpdate, IntPtr hrgnUpdate, int fuRedraw);

          protected override void OnSizeChanged(EventArgs e)
          {
          base.OnSizeChanged(e);
          // force redraw of the entire control (including non-client area)
          RedrawWindow(this.Handle, IntPtr.Zero, IntPtr.Zero, RDW_FRAME | RDW_UPDATENOW | RDW_INVALIDATE);
          }

          Works like a charm :) "I think I speak on behalf of everyone here when I say huh?" - Buffy

          J Offline
          J Offline
          John Fisher
          wrote on last edited by
          #4

          Great! John
          "You said a whole sentence with no words in it, and I understood you!" -- my wife as she cries about slowly becoming a geek.

          1 Reply Last reply
          0
          Reply
          • Reply as topic
          Log in to reply
          • Oldest to Newest
          • Newest to Oldest
          • Most Votes


          • Login

          • Don't have an account? Register

          • Login or register to search.
          • First post
            Last post
          0
          • Categories
          • Recent
          • Tags
          • Popular
          • World
          • Users
          • Groups