none
Painting right arrow in ToolStripComboBox RRS feed

  • Question

  • Hello all,
    This may seem rather silly, but for somereason I can't get it right...
    I've been trying to modify a ToolStripComboBox so that the arrow will point to the right rather than downwards with no success. I've tried overriding the  OnPaint method like that:


           
    Code Snippet

    protected override void OnPaint(PaintEventArgs e)
            {
                base.OnPaint(e);
                DrawTriangle(e.Graphics, Width, Height);
            }

            private void DrawTriangle(Graphics g, int width, int height)
            {
                GraphicsPath triangle = new GraphicsPath();
                int triWidth = 3;
                int rightMargin = 2;
                Point topLeft = new Point(width - triWidth - rightMargin, (height / 2) - 3);
                Point bottomLeft = new Point(width - triWidth - rightMargin, ((height / 2)) + 3);
                Point right = new Point(width - rightMargin, height / 2);
                triangle.AddLine(topLeft, bottomLeft);
                triangle.AddLine(bottomLeft, right);
                triangle.AddLine(right, topLeft);
                g.FillPath(new SolidBrush(Color.Black), triangle);
            }


    Then I tried using a CustomToolStripProfessionalRenderer that uses either OnRenderItemBackground or OnRenderArrow like this, while changing the menuStrip Renderer property to a new CustomToolStripRenderer :
    Code Snippet

    public class CustomToolStripRenderer : ToolStripProfessionalRenderer
        {
            protected override void OnRenderItemBackground(ToolStripItemRenderEventArgs e)
            {
                base.OnRenderItemBackground(e);
                if (e.Item is CustomToolStripCombo)
                    DrawTriangle(e.Graphics, e.Item.Width, e.Item.Height);
            }

            private void DrawTriangle(Graphics g, int width, int height)
            {
                GraphicsPath triangle = new GraphicsPath();
                int triWidth = 3;
                int rightMargin = 2;
                Point topLeft = new Point(width - triWidth - rightMargin, (height / 2) - 3);
                Point bottomLeft = new Point(width - triWidth - rightMargin, ((height / 2)) + 3);
                Point right = new Point(width - rightMargin, height / 2);
                triangle.AddLine(topLeft, bottomLeft);
                triangle.AddLine(bottomLeft, right);
                triangle.AddLine(right, topLeft);
                g.FillPath(new SolidBrush(Color.Black), triangle);
            }
        }


    which didn't work at all, so I tried forcing it by calling Owner.Renderer.DrawItemBackground (and for the OnRenderArrow method, Owner.Renderer.RenderArrow), which didn't do anything neither...

    Any ideas?
    Monday, August 20, 2007 12:21 PM

Answers

  • Very difficult to do, painting is implemented by the native Windows combobox control.  And you can't override the combobox in ToolStripComboBox.  Your first focus should be on creating a combobox that paints its own arrow.  That's hard enough, you'll need to sub-class it to trap the paint messages.  With that in place, you can then put it on a toolstrip with ToolStripControlHost.

    using System;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;

    public class MyCombo : ComboBox {
      private ComboSnoop mSnooper;
      protected override void WndProc(ref Message m) {
        if (m.Msg == 0x0002) {
          // WM_DESTROY, un-subclass control
          mSnooper.ReleaseHandle();
          mSnooper = null;
        }
        base.WndProc(ref m);
        if (m.Msg == 0x0001) {
          // WM_CREATE, subclass control
          mSnooper = new ComboSnoop(this);
          mSnooper.AssignHandle(this.Handle);
        }
      }
      private class ComboSnoop : NativeWindow {
        private MyCombo mCombo;
        private Rectangle mButton;
        public ComboSnoop(MyCombo combo) {
          mCombo = combo;
          COMBOBOXINFO info = new COMBOBOXINFO();
          info.cbSize = Marshal.SizeOf(info);
          SendMessageCb(mCombo.Handle, 0x164, IntPtr.Zero, out info);
          mButton = new Rectangle(info.rcButton.Left, info.rcButton.Top,
            info.rcButton.Right - info.rcButton.Left, info.rcButton.Bottom - info.rcButton.Top);
          mButton.Inflate(-1, -1);
        }
        protected override void WndProc(ref Message m) {
          base.WndProc(ref m);
          if (m.Msg == 0x000f) {
            // WM_PAINT processed, draw over the arrow
            using (Graphics gr = Graphics.FromHwnd(this.Handle)) {
              using (LinearGradientBrush bbr = new LinearGradientBrush(mButton, Color.White, Color.Silver, 0F))
                gr.FillRectangle(bbr, mButton);
              Point c = new Point(mButton.Left + mButton.Width / 2 + 2, mButton.Top + mButton.Height / 2);
              Point[] tri = new Point[] { new Point(c.X - 3, c.Y - 6), new Point(c.X + 3, c.Y), new Point(c.X - 3, c.Y + 6) };
              gr.FillPolygon(Brushes.DarkBlue, tri);
            }
          }
        }
      }
      // P/Invoke declarations
      private struct COMBOBOXINFO {
        public Int32 cbSize;
        public RECT rcItem;
        public RECT rcButton;
        public int buttonState;
        public IntPtr hwndCombo;
        public IntPtr hwndEdit;
        public IntPtr hwndList;
      }
      [StructLayout(LayoutKind.Sequential)]
      private struct RECT {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
      }
      [DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
      private static extern IntPtr SendMessageCb(IntPtr hWnd, int msg, IntPtr wp, out COMBOBOXINFO lp);
    }
    Monday, August 20, 2007 6:36 PM
    Moderator

All replies

  • In other words- is there a way to paint whatever on a ToolStripComboBox- or for that matter to any already existing ToolStripItem- by creating a CustomToolStripComboBox and overriding the paint method? or by creating CustomToolStripRenderer? (-if so, can anyone point me to an example?)
    Or will I have to write the whole thing from nothing (inheriting from a ToolStripItem) in order to get a ToolStripComboBox with a
    different
    look?
    Monday, August 20, 2007 2:42 PM
  • Very difficult to do, painting is implemented by the native Windows combobox control.  And you can't override the combobox in ToolStripComboBox.  Your first focus should be on creating a combobox that paints its own arrow.  That's hard enough, you'll need to sub-class it to trap the paint messages.  With that in place, you can then put it on a toolstrip with ToolStripControlHost.

    using System;
    using System.Drawing;
    using System.Drawing.Drawing2D;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;

    public class MyCombo : ComboBox {
      private ComboSnoop mSnooper;
      protected override void WndProc(ref Message m) {
        if (m.Msg == 0x0002) {
          // WM_DESTROY, un-subclass control
          mSnooper.ReleaseHandle();
          mSnooper = null;
        }
        base.WndProc(ref m);
        if (m.Msg == 0x0001) {
          // WM_CREATE, subclass control
          mSnooper = new ComboSnoop(this);
          mSnooper.AssignHandle(this.Handle);
        }
      }
      private class ComboSnoop : NativeWindow {
        private MyCombo mCombo;
        private Rectangle mButton;
        public ComboSnoop(MyCombo combo) {
          mCombo = combo;
          COMBOBOXINFO info = new COMBOBOXINFO();
          info.cbSize = Marshal.SizeOf(info);
          SendMessageCb(mCombo.Handle, 0x164, IntPtr.Zero, out info);
          mButton = new Rectangle(info.rcButton.Left, info.rcButton.Top,
            info.rcButton.Right - info.rcButton.Left, info.rcButton.Bottom - info.rcButton.Top);
          mButton.Inflate(-1, -1);
        }
        protected override void WndProc(ref Message m) {
          base.WndProc(ref m);
          if (m.Msg == 0x000f) {
            // WM_PAINT processed, draw over the arrow
            using (Graphics gr = Graphics.FromHwnd(this.Handle)) {
              using (LinearGradientBrush bbr = new LinearGradientBrush(mButton, Color.White, Color.Silver, 0F))
                gr.FillRectangle(bbr, mButton);
              Point c = new Point(mButton.Left + mButton.Width / 2 + 2, mButton.Top + mButton.Height / 2);
              Point[] tri = new Point[] { new Point(c.X - 3, c.Y - 6), new Point(c.X + 3, c.Y), new Point(c.X - 3, c.Y + 6) };
              gr.FillPolygon(Brushes.DarkBlue, tri);
            }
          }
        }
      }
      // P/Invoke declarations
      private struct COMBOBOXINFO {
        public Int32 cbSize;
        public RECT rcItem;
        public RECT rcButton;
        public int buttonState;
        public IntPtr hwndCombo;
        public IntPtr hwndEdit;
        public IntPtr hwndList;
      }
      [StructLayout(LayoutKind.Sequential)]
      private struct RECT {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
      }
      [DllImport("user32.dll", EntryPoint = "SendMessageW", CharSet = CharSet.Unicode)]
      private static extern IntPtr SendMessageCb(IntPtr hWnd, int msg, IntPtr wp, out COMBOBOXINFO lp);
    }
    Monday, August 20, 2007 6:36 PM
    Moderator