Please click this logo to help me get on their beta program:

Xobni outlook add-in for your inbox








26/8/2005

Drawing a selection rubber band like Explorer - improved

Filed under: General, Programming, .NET — Oliver Sturm @ 7:14 pm - 3 years, 2 months ago

I heard criticism about my first post on the topic: if a control was on the form, apart from the background drawing, the rubber rectangle would appear behind the control, not in front of it. Of course there are several ways to change this, I decided to implement one of them just for fun :-)

This is what things look like before and after my changes:

Here’s the complete code again, which will need the same changes as before if it is to be used in VS 2003 (see the comments on the first post for this).

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
	
namespace SelectionRectTest {
	public class Form1 : Form {
		public Form1( ) {
			InitializeComponent( );
			BackColor = Color.White;
		}
	
		protected override void OnPaint(PaintEventArgs e) {
			if (selectionPanel == null) {
				e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
	
				// Draw some stuff so the transparent selection becomes visible
				int height = ClientRectangle.Height;
				int heightTenth = height / 10;
				int width = ClientRectangle.Width;
				int widthTenth = width / 10;
	
				e.Graphics.DrawLines(Pens.Red, new Point[] {
					new Point(widthTenth, heightTenth * 3),
					new Point(widthTenth * 5, heightTenth),
					new Point(widthTenth * 8, heightTenth * 9),
					new Point(widthTenth, heightTenth * 3)
				});
				e.Graphics.DrawEllipse(Pens.Blue, widthTenth * 4, heightTenth * 3, widthTenth * 5, heightTenth * 4);
			}
		}
	
		protected override void OnResize(EventArgs e) {
			base.OnResize(e);
			Invalidate( );
		}
	
		Point mouseDownPos = Point.Empty;
	
		protected override void OnMouseDown(MouseEventArgs e) {
			base.OnMouseDown(e);
	
			if (e.Button == MouseButtons.Left)
				mouseDownPos = e.Location;
	
			Bitmap contentBitmap = new Bitmap(Bounds.Width, Bounds.Height);
			Rectangle targetRect = Bounds;
			targetRect.X = 0 ;
			targetRect.Y = 0;
			DrawToBitmap(contentBitmap, targetRect);
	
			Point originScreen = PointToScreen(new Point(0, 0));
			Rectangle usableRect = new Rectangle(new Point(originScreen.X - Bounds.X, originScreen.Y - Bounds.Y), ClientRectangle.Size);
	
			selectionPanel = new SelectionPanel(contentBitmap, usableRect, mouseDownPos);
			Controls.Add(selectionPanel);
			selectionPanel.BringToFront( );
		}
	
		SelectionPanel selectionPanel;
	
		protected override void OnMouseMove(MouseEventArgs e) {
			base.OnMouseMove(e);
			if (e.Button == MouseButtons.Left && mouseDownPos != Point.Empty && selectionPanel != null)
				selectionPanel.Invalidate( );
		}
	
		protected override void OnMouseUp(MouseEventArgs e) {
			base.OnMouseUp(e);
			mouseDownPos = Point.Empty;
			Controls.Remove(selectionPanel);
			selectionPanel = null;
		}
	
		private Button button1;
	
		private void InitializeComponent( ) {
			this.button1 = new System.Windows.Forms.Button( );
			this.SuspendLayout( );
			//
			// button1
			//
			this.button1.Location = new System.Drawing.Point(114, 108);
			this.button1.Name = "button1";
			this.button1.Size = new System.Drawing.Size(75, 33);
			this.button1.TabIndex = 0;
			this.button1.Text = "button1";
			//
			// Form1
			//
			this.ClientSize = new System.Drawing.Size(292, 266);
			this.Controls.Add(this.button1);
			this.Name = "Form1";
			this.ResumeLayout(false);
	
		}
	}
	
	public class SelectionPanel : Panel {
		public SelectionPanel(Bitmap backgroundBitmap, Rectangle usableRect, Point mouseDownPos) {
			DoubleBuffered = true;
	
			this.backgroundBitmap = backgroundBitmap;
			this.mouseDownPos = mouseDownPos;
			this.usableRect = usableRect;
	
			this.Size = usableRect.Size;
			this.Location = new Point(0, 0);
	
			selectionColor = Color.FromArgb(200, 0xE8, 0xED, 0xF5);
			selectionBrush = new SolidBrush(selectionColor);
			frameColor = Color.FromArgb(0x33, 0x5E, 0xA8);
			framePen = new Pen(frameColor);
		}
	
		Bitmap backgroundBitmap;
		Point mouseDownPos;
		Color selectionColor;
		Brush selectionBrush;
		Color frameColor;
		Pen framePen;
		Rectangle usableRect;
	
		protected override void OnPaint(PaintEventArgs e) {
			// copy the backgroundImage
			e.Graphics.DrawImage(backgroundBitmap, ClientRectangle, usableRect, GraphicsUnit.Pixel);
	
			// Now, if needed, draw the selection rectangle
			if (mouseDownPos != Point.Empty) {
				Point mousePos = PointToClient(MousePosition);
				Rectangle selectedRect = new Rectangle(
					Math.Min(mouseDownPos.X, mousePos.X),
					Math.Min(mouseDownPos.Y, mousePos.Y),
					Math.Abs(mousePos.X - mouseDownPos.X),
					Math.Abs(mousePos.Y - mouseDownPos.Y));
				e.Graphics.FillRectangle(selectionBrush, selectedRect);
				e.Graphics.DrawRectangle(framePen, selectedRect);
			}
		}
	}
}

4 Comments »

  1. The DrawToBitmap method is unavailable before .NET V2. :-(

    Comment by Larry — 26/8/2005 @ 8:49 pm - 3 years, 2 months ago

  2. You are right, but that’s easy to work around. Just put the following code in the form class and call the DotNet1DrawToBitmap method instead of the “normal” DrawToBitmap. You don’t need the targetRect in that case.

      [DllImport("user32.dll")]
      static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, DrawingOptions drawingOptions);
      const int WM_PRINT = 0x317;
    	
      [Flags]
      enum DrawingOptions {
        PRF_CHECKVISIBLE = 0x01,
        PRF_NONCLIENT = 0x02,
        PRF_CLIENT = 0x04,
        PRF_ERASEBKGND = 0x08,
        PRF_CHILDREN = 0x10,
        PRF_OWNED = 0x20
      }
    	
      void DotNet1DrawToBitmap(Bitmap bitmap) {
        using (Graphics gr = Graphics.FromImage(bitmap)) {
          IntPtr hdc = gr.GetHdc( );
          SendMessage(Handle, WM_PRINT, hdc, DrawingOptions.PRF_CHILDREN | DrawingOptions.PRF_ERASEBKGND |
            DrawingOptions.PRF_CLIENT | DrawingOptions.PRF_NONCLIENT);
          gr.ReleaseHdc(hdc);
        }
      }
    

    Comment by Oliver Sturm — 27/8/2005 @ 12:27 pm - 3 years, 2 months ago

  3. Nice code. However, windows explorer updates the selected items while the user is selecting them. It would be nice if you could implement that in this control

    Comment by Garf — 12/3/2007 @ 10:54 am - 1 year, 8 months ago

  4. Hey Garf - well, this is not a control, it’s just a sample to show you how it can be done. It’s also rather generic - it doesn’t assume what you’re going to do with it. If your own application is something like Windows Explorer, then sure, selection like Explorer makes sense to you. For others it wouldn’t…
    That said, it’s really rather easy to do. You just have to have a list of rects for the elements you want to make selectable, and every time the selection rect changes, you check for overlaps between any of the selectable rects in your list and the actual selection rect.
    If I ever find the time, I might have a shot at this… but I remember there were still other ideas I was planning to follow up on, so this may not be highest on my priority list. Plus, if I ever have time…

    Comment by Oliver Sturm — 12/3/2007 @ 11:10 am - 1 year, 8 months ago

RSS feed for comments on this post. TrackBack URI

Leave a comment

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>


Powered by WordPress
© Copyright 2005-2008 Oliver Sturm