Windows Develop Bookmark and Share   
 index > Windows Forms Designer > Undo of Control Resize does not work properly
 

Undo of Control Resize does not work properly

I am creating a control that displays a GraphicsPath object and exposesthe PathPointsfor editing. When the control is resized, I want the GraphicsPath to scale with the control. I have included a stripped-down version of the control to show how I am resizing:

    public class EditPointsControl2 : UserControl
    {
        const int iMinWidth = 30, iMinHeight = 30;
        PointF[] editPoints = new PointF[] { 
            new PointF(15f, 1f), new PointF(1f, 29f), new PointF(29f, 29f) };
        byte[] pointStyles = new byte[] { 1, 1, 1 }; // 0x01 is PathPointType.Line

        bool bResizePathWithControl = false;
        Size szOriginalSize = new Size(iMinWidth, iMinHeight);

        public EditPointsControl2()
        {
            this.Size = new Size(iMinWidth, iMinHeight);
        }

        public PointF[] EditPoints
        {
            get { return editPoints; }
            set { editPoints = value; }
        }

        public byte[] EditPointStyles
        {
            get { return pointStyles; }
            set { pointStyles = value; }
        }

        protected override void OnLoad(EventArgs e)
        {
            szOriginalSize = this.Size;
            bResizePathWithControl = true;
            base.OnLoad(e);
        }
        
        protected override void OnResize(EventArgs e)
        {
            if (!bResizePathWithControl)
            {
                szOriginalSize = this.Size;
                return;
            }

            // Resize the path according to the scaled change in the control size.
            using (GraphicsPath gp = new GraphicsPath(editPoints, pointStyles))
            {
                float fWidthRatio = (float)this.Size.Width / (float)szOriginalSize.Width;
                float fHeightRatio = (float)this.Size.Height / (float)szOriginalSize.Height;
                using (Matrix m = new Matrix())
                {
                    m.Scale(fWidthRatio, fHeightRatio);
                    gp.Transform(m);
                }
                editPoints = gp.PathPoints;
                pointStyles = gp.PathTypes;
            }
            szOriginalSize = this.Size;
                        
            base.OnResize(e);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            using (GraphicsPath gp = new GraphicsPath(editPoints, pointStyles))
            {
                Pen pOutline = new Pen(Color.Black, 1f);
                e.Graphics.DrawPath(pOutline, gp);
                pOutline.Dispose();
            }

            base.OnPaint(e); 
        }
    }



The problem comes when I try to Undo a resize in the designer window. If I resize by setting the Size property in the Properties grid, I can undo the resize and the points go back properly to their original positions. However, if I resize using the grabhandles, trying to undo the change causes erratic behavior. For example, if I use the right-side grabhandle to double the width of the control, the graphics path scales up properly to double its size. However, when I click the Undo button, the control goes back to its original width, but the graphics path goes to half of its original width.

Based on some playing I have done, I suspect this is because the Undo transaction first resets the EditPoints property of my controlback to its original value, resizing the graphics path back to its original size.Then it resizes the control back to its original width, further shrinking the already-reset path to half its original width.

I'm not sure how to get around this. What I really need to do is either prevent the Undo from resetting EditPoints prior to resizing the control, or to recognize that the control is being resized by the Undo, and prevent the second resize of the graphics path. Unfortunately, I have no idea how to do either of these.

Does anyone have any ideas?

Thanks,
Doug
Douglas Hauck  Friday, August 28, 2009 1:51 PM
Do you need your EditPoints property to have a setter? Well, maybe you do because it is an array of PointF. But what if it were a List<PointF>? If it were a list, then you would create the new list object in the constructor, and then get rid of the EditPoints setter. Any modifications to the array of EditPoints would be handled via the Add() and Remove() methods of the list object. If you need to provide design-time support for the EditPoints property, you can create a derived CollectionEditor UI editor.
MCP
  • Proposed As Answer bywebJose Wednesday, September 23, 2009 4:59 AM
  • Unproposed As Answer byDouglas Hauck Wednesday, September 23, 2009 11:55 AM
  •  
webJose  Friday, September 04, 2009 2:30 PM

Well, I had a great big post about the new solution I am working on to fix this problem, but I glitched and deleted the whole thing. I'm not typing that up again, so here is the short version:

- I need the setter, even if I switched from an array to a List, because I want the EditPoints persisted in Designer.cs.

- My solution instead is not to modify the EditPoints in OnResize. EditPoints can only be modified by the PointEditor dialog, available via a DesignerActionItem.

- The Points Editor will also store a BaseSize value.

- In OnPaint, I scale the GraphicsPath I already create from the EditPoints tothis.Size / BaseSize, right before drawing it.

- This has its own issues, but I think it's a better solution. When I get the problems sorted out, I'll post a clean copy.

Sorry for the terse format, but man, I just couldn't bring myself to type all that again. Thanks, though, to WJ for the suggestion.

Douglas Hauck  Wednesday, September 23, 2009 12:10 PM
You can get your list serialized by applying the DesignerSerializationVisibilityAttribute attribute to the property of endpoints. This is the same thing that say, the ListView does for its Items and Columns collections, which are lists that have no setter, yet they persist their contents.

http://msdn.microsoft.com/en-us/library/system.componentmodel.designerserializationvisibilityattribute.designerserializationvisibilityattribute.aspx

With the above attribute, you can get rid of the property setter and still have the contents of the collection persisted in the designer.


MCP
webJose  Wednesday, September 23, 2009 12:38 PM
You can get your list serialized by applying the DesignerSerializationVisibilityAttribute attribute to the property of endpoints. This is the same thing that say, the ListView does for its Items and Columns collections, which are lists that have no setter, yet they persist their contents.

http://msdn.microsoft.com/en-us/library/system.componentmodel.designerserializationvisibilityattribute.designerserializationvisibilityattribute.aspx

With the above attribute, you can get rid of the property setter and still have the contents of the collection persisted in the designer.


MCP

Sure - I already use the DSV.Hidden and DSV.Content attribute values extensively. For one thing, I am still using VS2005, which has a problem with serializing Collections of non-native classes. Adorning Collection-type properties with a DSV.Content attribute can make them work most of the time.

However, even so, I still need a set accessorin my EditPoints property to retrieve serialized data. If you look at List-type properties in the Designer.cs page, you will see that even when the DSV.Content attribute causes the Designer to build the List using List.Add, it still uses the set_Property accessor to get the List it built into the actual object's property. At least in the applications that I have seen, anyway. Have you seen different?

Thanks,
Doug
Douglas Hauck  Wednesday, September 23, 2009 2:05 PM
Douglas:

This is the generated code for a DataGridView regarding the addition of columns in design-time:

            this.regionIDDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.nameDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
            this.isActiveDataGridViewCheckBoxColumn = new System.Windows.Forms.DataGridViewCheckBoxColumn();
            this.dataGridView1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
            this.regionIDDataGridViewTextBoxColumn,
            this.nameDataGridViewTextBoxColumn,
            this.isActiveDataGridViewCheckBoxColumn});

There is no attempt by the designer to insert code to set the Columns property. This is because the Columns property does not have a Set accessor. If you remove your Set accessor you should have no problems. I have not done this in a control from scratch. I have done it in a derived ListView control where I had to provide new implementations of the Items and Columns properties. I had no issues.

If the collection contained is of a user-defined type, and that user-defined type requires a constructor call, like the columns example above, you should provide a TypeConverter class for your type that can convert to InstanceDescriptor . This is basically the way the designer knows that it needs to create new instances of the class before attempting to add them. This should therefore allow you to serialize the contents of a read-only property that represents a list, in your case, of EndPoints.
MCP
webJose  Wednesday, September 23, 2009 3:32 PM

Collection properties should not have a Set accessor. You wish to edit the content of the Collection you do not wish to replace the Collection itself.

If you create a class which the IDE does not know how to serialize then, as WebJose has pointed out, you will need to add a TypeConverter which returns an InstanceDescriptorfor that class. It will then serialize just fine with the DesignerSerializationVisibility attribute set to Content.

There is an issue with controls using Custom TypeConverters (as in the example you linked to), but these are mostly sorted out by editing AssemblyInfo.cs so that the AssemblyVersion attribute auto increments upon each build. This issue is still around in VS2008.


Mick Doherty
http://dotnetrix.co.uk
Mick Doherty  Wednesday, September 23, 2009 6:10 PM

Ok, fellas, I need some time to digest this. I have always seen an assignment statement for my List properties in Designer.cs, but then again, I have always provided set accessors for them. I will create one without the set accessor and see what happens, then report back. In the meantime, thanks for the advice.

Doug

Douglas Hauck  Wednesday, September 23, 2009 6:27 PM

You can use google to search for other answers

Custom Search

More Threads

• Error as "Falied to import the ActiveX control. Please ensure it is properly registered." inside Windows Form Designer
• Usercontrol with a DataSource property (winform!) ???
• Continuous Forms
• Custom Form with a Panel to act as a Container
• ASP.NET Windows Forms
• resizeable
• ImageList Serialization for Design time
• Design time support for protected property
• cannot delete controls ! :-O
• Designer keeps adding tablestyles in VS 2003.