3

To take a previous question a bit further, namely, How do I draw the outline of a collection of rectangles?, I would like to know if it is possible to draw the outline of a region with a dashed line, not solid?

I have a class called Deck that contains a Region object. The constructor creates a large rectangle, supposed to represent the surface of a floor.

public Deck( double x, double y, double width, double height )
{
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;

    region = new Region( new RectangleF( (float)x, (float)y,  (float)width, (float)height ) );
}

Then, I want the user to be able to either "cut" holes in the floor, or "fill" up the holes, but allowing that whatever rectangle shape gets cut out, perhaps only a portion of it can be put back in. For this I created methods to that basically wrap the Union and Exclude methods to either add or remove rectangles from the region.

public void RemoveA( double x, double y, double width, double height )
{
    var rect = new RectangleF( (float)x, (float)y, (float)width, (float)height );
    region.Exclude( rect );
}

public void AddA( double x, double y, double width, double height )
{
    var rect = new RectangleF( (float)x, (float)y, (float)width, (float)height );
    region.Union( rect );
}

Then to draw the outline of the Deck and the cutouts (the region basically) I use the following code that I got from Peter's answer to the aforementioned question

[DllImport( @"gdi32.dll" )]
static extern bool FrameRgn( IntPtr hDC, IntPtr hRgn, IntPtr hBrush, int nWidth, int nHeight );
public void Draw( Graphics g, Color c, float scale )
{
    COLORREF colorref = new COLORREF( c );
    IntPtr hdc = IntPtr.Zero, hbrush = IntPtr.Zero, hrgn = IntPtr.Zero;

    try
    {
        hrgn = this.region.GetHrgn( g );
        hdc = g.GetHdc();
        hbrush = CreateSolidBrush( colorref.colorref );

        FrameRgn( hdc, hrgn, hbrush, 2, 2 );
    }
    finally
    {
        if (hrgn != IntPtr.Zero)
        {
            region.ReleaseHrgn( hrgn );
        }

        if (hbrush != IntPtr.Zero)
        {
            DeleteObject( hbrush );
        }

        if (hdc != IntPtr.Zero)
        {
            g.ReleaseHdc( hdc );
        }
    }
}

This worked great, but I wanted to see if I could find a way to draw the outline with a dashed or broken line, went through the documentation on gdi32.dll and other forums, but couldn't find anything that allowed me to use a dashed line.

I then tried to use a GraphicsPath instead of a Region, but that started to create other problems trying to determine when a GraphicsPath sees a Rectangle as being added, or removed, depending on its FillMode. This is the test app I ran.

[DllImport( @"gdiplus.dll" )]
public static extern int GdipWindingModeOutline( HandleRef path, IntPtr matrix, float flatness );
GraphicsPath gPath = null;
public Form1()
{
    InitializeComponent();

    gPath = new GraphicsPath();
    // draw the main rectangle (Deck) that the user can then cut recangular holes out of or add back in
    gPath.AddRectangle( new RectangleF( 10, 10, 150, 150 ) );

    // cut holes in the main rectangle, default FillMode (Alternate)
    gPath.AddRectangle( new System.Drawing.Rectangle( 30, 30, 60, 90 ) );
    // make this rectangle to overlap the one above, on purpose
    gPath.AddRectangle( new System.Drawing.Rectangle( 80, 30, 60, 90 ) );

    this.Paint += Form1_Paint;
}

private void Form1_Paint( object sender, PaintEventArgs e )
{
    using (Pen outlinePen = new Pen( Color.Red, 2 ))
    {
            outlinePen.DashStyle = DashStyle.Dash;
            e.Graphics.DrawPath( outlinePen, gPath );
    }
}

The result I get is the outline of the larger rectangle, but the other 2 "hole" rectangles overlap, no good.

Result 1

I can get rid of the overlap if I use the GpidWindingModeOutline approach in my Paint method

private void Form1_Paint( object sender, PaintEventArgs e )
{
    HandleRef handle =
       new HandleRef( gPath, (IntPtr)gPath.GetType().GetField( "nativePath", BindingFlags.NonPublic | BindingFlags.Instance ).GetValue( gPath ) );
    //Change path so it only contains the outline    
    GdipWindingModeOutline( handle, IntPtr.Zero, 0.25F );

    using (Pen outlinePen = new Pen( Color.FromArgb( 255, Color.Red ), 2 ))
    {
        outlinePen.DashStyle = DashStyle.Dash;
        e.Graphics.DrawPath( outlinePen, gPath );
    }
}

Still no good though, because all I see now is the main outer rectangle.

Then another complication arises when the rectangles share an edge, or portion of an edge. The GraphicsPath then draws a solid line on this shared edge, whereas a Region object wouldn't draw one at all, which is what I want.

Result 2

I realize I could perform checks for overlapping Rectangles, merging ones that share and edge etc, it just becomes complicated when rectangles only share perhaps a portion of an edge or overlap on a corner. A Region takes care of all of that which is great, just a pity there doesn't appear to be a way to draw the outline with a dashed line.

Community
  • 1
  • 1
Chippie
  • 51
  • 5
  • You can extend the original question rather than creating separate question for this. – Avinash Jain Dec 01 '15 at 10:49
  • Could you perhaps advise me as to how to extend the original question? I'm new to this site, do I just add a comment? Then I will delete this question. – Chippie Dec 01 '15 at 12:24
  • you have edit option at the bottom of question. you can use it to add more content to your question – Avinash Jain Dec 01 '15 at 12:30
  • 1
    I would recommend _against_ "extending" an existing question. And you _especially_ should not "extend" someone _else's_ question. It is perfectly appropriate to post a follow-up question (following up someone else's or your own), as you've done here. However, you need to make sure your question is a good one. That means you will include a good [mcve] that shows clearly what you've tried, along with a precise explanation of what that code does and how that's different from what you want. Please improve _this_ question if you would like an answer to your "extension" of the other question. – Peter Duniho Dec 01 '15 at 22:31
  • As for the question as stated so far: _"I would like to know if it is possible to draw the outline of a region with a dashed line, not solid?"_ -- no, not using that same technique, not really. You have to use a brush for the frame, and while you can use a patterned brush in a way that will give a sort-of-dashed look, it won't be the same as using an actual dashed-line pen. But WPF can easily handle that, using `PathGeometry` (a WPF `Geometry` can be combined with union/intersect/exclude similar to a region, but you can draw `Geometry` with dashed strokes). – Peter Duniho Dec 01 '15 at 22:38
  • @PeterDuniho Thanks for the reply, yes I was really reluctant to edit someone else's work, didn't want to mess it up. I re-edited my question to show what I tried, hopefully it's clearer now, sorry for that. I will look into what you suggested regarding using the WPF class `PathGeometry`. – Chippie Dec 02 '15 at 08:34
  • Note that you may run into similar issues with `Geometry` combine operations as you report above for `GraphicsPath` in GDI+. Hard to say for sure without a good [mcve]. It can be tricky getting the fills to work as desired when combining geometries. In either case, you might have some success using clipping instead of region exclusion to remove areas, depending on just how these operations are being selected and managed. – Peter Duniho Dec 02 '15 at 08:47

0 Answers0