Scroll-proofing your DIVs

No Comments

So, you want to lock a div on the screen left-to-right? Are your users guaranteed to be using Internet Explorer? Well, then! I might a quick solution for you. I used this very simple style-sheet entry to keep a legend at the same place on the screen while the user scrolled through hundreds of table entries in a grid.

The Div

1
<div class="legend"> <!--- put your legend here ---> </div>
<div class="legend"> <!--- put your legend here ---> </div>

The StyleSheet

1
2
3
4
5
6
.legend 
{
     position: relative;
     left: expression(parentNode.scrollLeft);
     z-index: 30; 
}
.legend 
{
     position: relative;
     left: expression(parentNode.scrollLeft);
     z-index: 30; 
}

Note that you need to use as many “parentNode”s as possible to drill upward until you get to the root document. (i.e. parentNode.parentNode.parentNode.(…).parentNode.scrollLeft)

It’s very handy, and works very well in Internet Explorer. If you need to do something similar in Firefox or Chrome… this won’t work. It won’t leave really bad results, but it also won’t do what you expect it to. Sorry.

Sort your Databound Grid effortlessly!

No Comments

More work today. More cleaning. More sorting. It has become somewhat of a bother to keep up with so many versions of sorting grids, so I took some time today to really nail down a great sorting class that handles nothing but grids and their data. By the time I was done, I was utterly and completely shocked at how simple and clean this class ended up being. And to top it off, it handles any databound grid that I have. (NOTE: Your experience may vary, but this might be a great launching point for you to build your own.)

First, I know that all of my databound grid bind to objects in memory, not to database tables. They are, in fact, all nothing but lists of one type or another. Using a little reflection magic, I can sort the underlying list, rebind the control, and TA-DA! A sorted grid.

First, I created a class that will (hopefully) handle all of the cases.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    internal class GlobalSort : IComparer<object>
    {
        internal string PropertyName = "";
        internal bool Ascending = true;
 
        internal static string LastColumnSorted = "";
        internal static bool LastAscending = true;
 
        internal static void SortBindingSource(int idx, DataGridView dgv)
        {
            if (dgv.DataSource is BindingSource)
            {
                BindingSource bs = (BindingSource)dgv.DataSource;
                string name = dgv.Columns[idx].DataPropertyName;
                bool LastColumnMatched = (LastColumnSorted == name);
                bool Ascend = true;
 
                if (LastColumnMatched) Ascend = !LastAscending;
 
                // Keep track of these for next time.
                LastAscending = Ascend;
                LastColumnSorted = name;
 
                List<object> list = new List<object>();
                foreach (object obj in bs)
                {
                    list.Add(obj);
                }
 
                list.Sort(new GlobalSort() 
                    { PropertyName = name, Ascending = Ascend });
 
                bs.Clear();
                foreach (object obj in list)
                {
                    bs.Add(obj);
                }
                bs.ResetBindings(false);
            }
        }
 
        #region IComparer<object> Members
 
        public int Compare(object a, object b)
        {
            if (string.IsNullOrEmpty(PropertyName.Trim())) return 0;
            if (a == null || b == null) return 0;
 
            Type t1 = a.GetType();
            Type t2 = b.GetType();
 
            try
            {
                PropertyInfo pi1 = t1.GetProperty(PropertyName);
                PropertyInfo pi2 = t2.GetProperty(PropertyName);
 
                object c1 = pi1.GetValue(a, null);
                object c2 = pi2.GetValue(b, null);
 
                if (c1 is IComparable && c2 is IComparable)
                {
                    if (Ascending)
                        return ((IComparable)c1).CompareTo(c2);
                    else
                        return ((IComparable)c2).CompareTo(c1);
                }
                else
                {
                    return 0;
                }
            }
            catch
            {
                return 0;
            }
        }
 
        #endregion
    }
    internal class GlobalSort : IComparer<object>
    {
        internal string PropertyName = "";
        internal bool Ascending = true;

        internal static string LastColumnSorted = "";
        internal static bool LastAscending = true;

        internal static void SortBindingSource(int idx, DataGridView dgv)
        {
            if (dgv.DataSource is BindingSource)
            {
                BindingSource bs = (BindingSource)dgv.DataSource;
                string name = dgv.Columns[idx].DataPropertyName;
                bool LastColumnMatched = (LastColumnSorted == name);
                bool Ascend = true;

                if (LastColumnMatched) Ascend = !LastAscending;

                // Keep track of these for next time.
                LastAscending = Ascend;
                LastColumnSorted = name;

                List<object> list = new List<object>();
                foreach (object obj in bs)
                {
                    list.Add(obj);
                }

                list.Sort(new GlobalSort() 
                    { PropertyName = name, Ascending = Ascend });

                bs.Clear();
                foreach (object obj in list)
                {
                    bs.Add(obj);
                }
                bs.ResetBindings(false);
            }
        }

        #region IComparer<object> Members

        public int Compare(object a, object b)
        {
            if (string.IsNullOrEmpty(PropertyName.Trim())) return 0;
            if (a == null || b == null) return 0;

            Type t1 = a.GetType();
            Type t2 = b.GetType();

            try
            {
                PropertyInfo pi1 = t1.GetProperty(PropertyName);
                PropertyInfo pi2 = t2.GetProperty(PropertyName);

                object c1 = pi1.GetValue(a, null);
                object c2 = pi2.GetValue(b, null);

                if (c1 is IComparable && c2 is IComparable)
                {
                    if (Ascending)
                        return ((IComparable)c1).CompareTo(c2);
                    else
                        return ((IComparable)c2).CompareTo(c1);
                }
                else
                {
                    return 0;
                }
            }
            catch
            {
                return 0;
            }
        }

        #endregion
    }

There are three main points to note in this class. First, it has a static method that does all of the heavy lifting. It does the binding, the comparisons, and the reflection to make it all sing. In the end, it also calls the “sort” on my list of data.

Secondly, it has an IComparer so that I can leverage the standard Sort() method of Lists within the static method mentioned above.

And finally, it has a static string that keeps track of the last column sorted. If you sort a column, then sort it again, it should reverse the sort order.

The beauty of this solution is that it uses the internal IComparable of each base type to do the sorting. Dates sort in date order. Strings sort in alphanumeric order. Numbers sort in numeric order. Everything sorts just the way it is supposed to!

Now, in the code for each datagrid, the internals become very simple.

1
2
3
4
    private void DataGridColumn_MouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
        GlobalSort.SortBindingSource(e.ColumnIndex, (DataGridView)sender);
    }
    private void DataGridColumn_MouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
        GlobalSort.SortBindingSource(e.ColumnIndex, (DataGridView)sender);
    }

All of my datagrids now only use one line of code for column mouse clicks. Done. Uber-simple and works like a dream. Try it out in your own code and let me know how it works.

Databinding Nightmare Revealed

No Comments

Over the past week, I’ve been fighting with two classes that are databound on the same form. For the majority of the week, I thought that the two databinding objects were conflicting with each other. You know, clsXyzBindingSource is some how ripping control away from clsAbcBindingSource.

The problem came in when I would update a the first field, all of the remaining fields should auto-populate because they are all changed in the actual object under the hood, but they didn’t. When I checked the object, it hadn’t changed either, even though the control changed. WHAT THE HECK!?!?

I was fuming mad after spending two days struggling with this stupid bug. I read every single page I could find on databinding done right. It’s a simple concept, why is this so hard?

Well… because there are some unspoken rules (apparently) about how you name things when you do databinding. It had absolutely NOTHING to do with the fact that I had two databound sets on my form. That was just the thing that distracted me from the root cause.

For the sake of everyone who may read this, let me make the unspoken spoken. Let me say it loud and clear: Don’t use the phrase “Changed” in ANY of your handler events!! It will cause databinding to break if it happens to be named the same as a field it is expecting.

Consider the class DataboundObj:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public class DataboundObj : UserControl
    { 
        public delegate void ValueBChangedHandler(DataboundObj sender); 
        public event ValueBChangedHandler ValueBChanged = delegate { }; 
        public DataboundObj() 
        { 
            InitializeComponent(); 
        } 
        public int Multiplier { get; set; } 
        public int ValueA { get; set; } 
        
        [Bindable(true)] 
        public int ValueB 
        { 
            get 
            { 
                return ValueA * Multiplier; 
            } 
            set 
            { 
                ValueA = value / Multiplier; ValueBChanged(this); 
            } 
        } 
    }
    public class DataboundObj : UserControl
    { 
        public delegate void ValueBChangedHandler(DataboundObj sender); 
        public event ValueBChangedHandler ValueBChanged = delegate { }; 
        public DataboundObj() 
        { 
            InitializeComponent(); 
        } 
        public int Multiplier { get; set; } 
        public int ValueA { get; set; } 
        
        [Bindable(true)] 
        public int ValueB 
        { 
            get 
            { 
                return ValueA * Multiplier; 
            } 
            set 
            { 
                ValueA = value / Multiplier; ValueBChanged(this); 
            } 
        } 
    }

 

This is PERFECTLY leagal code. But not when databinding. Why? Because the event called ValueBChanged causes all kinds of grief. Even if you make the class inherit INotifyProperyChanged, and handle that properly, databinding ceases to work properly. It can still receive data, but it will never, ever send data back.

The fix to this problem wasn’t to implement a better interface, or handle anything any differently. The fix was blindingly simple, but completely unforseeable unless you know about the (somefield)Changed rule of naming your events.

I changed the Handler line as follows, and everything works like a dream.

 public event ValueBChangedHandler ValueBModified = delegate { }; 

My Roundrect Object

No Comments

I’m developing a new user interface under winforms, and I need to implement a new control. The problem I’m having is that this control isn’t like any standard control. That is to say, I can’t just grab a “button” object and tweak how it works under the hood. I need to develop my own visual objects.

In order to do that, i thought it would be good to give all of the objects on my form the same look-and-feel. I wanted to make them all use roundrects — rectangles with soft, rounded corners instead of sharp angles. This is a look that many user controls and operating-systems have implemented for years. But I know that there aren’t any “round rect” functions in the core .NET framework.

So here’s a classic example of how to build your own. You can even use extension methods to place it “logically” into the .NET framework where you would normally expect it to be.

First, we have to declare a static class where we can place all of our extension methods. The methods defined here will all be extension methods that will point back to the Graphics object or the methods that support the extension methods.

1
2
3
4
    public static class RoundRectExtension
    {
        ...
    }
    public static class RoundRectExtension
    {
        ...
    }

Next we need to declare a private static method that my extension method can call that will define a graphics path. This GraphicsPath object is the path around the outside edge of the RoundRect.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
        private static GraphicsPath BuildRoundRectPath(RectangleF Rf, float radius)
        {
            GraphicsPath gp = new GraphicsPath();
 
            // First, make sure the cap isn't going 
            // to be bigger than the rectangle can support.
            if (radius > Rf.Width / 2) radius = Rf.Width / 2;
            if (radius > Rf.Height / 2) radius = Rf.Height / 2;
 
            // Draw a line 1, cap 1, line 2, cap 2, etc...
            // leaving room for the caps at the end of each line
            gp.AddLine(Rf.X + radius, Rf.Y,
                Rf.X + Rf.Width - (radius * 2), Rf.Y);
            gp.AddArc(Rf.X + Rf.Width - (radius * 2), Rf.Y,
                radius * 2, radius * 2, 270, 90);
 
            gp.AddLine(Rf.X + Rf.Width, Rf.Y + radius,
                Rf.X + Rf.Width, Rf.Y + Rf.Height - (radius * 2));
            gp.AddArc(Rf.X + Rf.Width - (radius * 2), Rf.Y + Rf.Height - (radius * 2),
                radius * 2, radius * 2, 0, 90);
 
            gp.AddLine(Rf.X + Rf.Width - (radius * 2),
                Rf.Y + Rf.Height, Rf.X + radius, Rf.Y + Rf.Height);
            gp.AddArc(Rf.X, Rf.Y + Rf.Height - (radius * 2),
                radius * 2, radius * 2, 90, 90);
 
            gp.AddLine(Rf.X, Rf.Y + Rf.Height - (radius * 2),
                Rf.X, Rf.Y + radius);
            gp.AddArc(Rf.X, Rf.Y, radius * 2,
                radius * 2, 180, 90);
 
 
            gp.CloseFigure();
            return gp;
        }
        private static GraphicsPath BuildRoundRectPath(RectangleF Rf, float radius)
        {
            GraphicsPath gp = new GraphicsPath();

            // First, make sure the cap isn't going 
            // to be bigger than the rectangle can support.
            if (radius > Rf.Width / 2) radius = Rf.Width / 2;
            if (radius > Rf.Height / 2) radius = Rf.Height / 2;

            // Draw a line 1, cap 1, line 2, cap 2, etc...
            // leaving room for the caps at the end of each line
            gp.AddLine(Rf.X + radius, Rf.Y,
                Rf.X + Rf.Width - (radius * 2), Rf.Y);
            gp.AddArc(Rf.X + Rf.Width - (radius * 2), Rf.Y,
                radius * 2, radius * 2, 270, 90);

            gp.AddLine(Rf.X + Rf.Width, Rf.Y + radius,
                Rf.X + Rf.Width, Rf.Y + Rf.Height - (radius * 2));
            gp.AddArc(Rf.X + Rf.Width - (radius * 2), Rf.Y + Rf.Height - (radius * 2),
                radius * 2, radius * 2, 0, 90);

            gp.AddLine(Rf.X + Rf.Width - (radius * 2),
                Rf.Y + Rf.Height, Rf.X + radius, Rf.Y + Rf.Height);
            gp.AddArc(Rf.X, Rf.Y + Rf.Height - (radius * 2),
                radius * 2, radius * 2, 90, 90);

            gp.AddLine(Rf.X, Rf.Y + Rf.Height - (radius * 2),
                Rf.X, Rf.Y + radius);
            gp.AddArc(Rf.X, Rf.Y, radius * 2,
                radius * 2, 180, 90);


            gp.CloseFigure();
            return gp;
        }

Finally, we can simply define our new extension methods for DrawRoundRect and FillRoundRect.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        public static void FillRoundRect(this Graphics G, Brush b, RectangleF Rf, float radius)
        {
            GraphicsPath gp = BuildRoundRectPath(Rf, radius);
 
            G.FillPath(b, gp);
 
            gp.Dispose();
        }
 
        public static void DrawRoundRect(this Graphics G, Pen p, RectangleF Rf, float radius)
        {
            GraphicsPath gp = BuildRoundRectPath(Rf, radius);
 
            G.DrawPath(p, gp);
 
            gp.Dispose();
        }
        public static void FillRoundRect(this Graphics G, Brush b, RectangleF Rf, float radius)
        {
            GraphicsPath gp = BuildRoundRectPath(Rf, radius);

            G.FillPath(b, gp);

            gp.Dispose();
        }

        public static void DrawRoundRect(this Graphics G, Pen p, RectangleF Rf, float radius)
        {
            GraphicsPath gp = BuildRoundRectPath(Rf, radius);

            G.DrawPath(p, gp);

            gp.Dispose();
        }

Now I can use my graphics object to draw rounded-rectangles as if it were part of the .NET language. In my other source code files, I can call it as normal:

1
2
3
4
5
6
7
8
9
10
11
12
        public void DrawMyNewRoundRectButton(Graphics g, Pen p, Brush b, RectangleF r, float radius)
        {
            g.FillRoundRect(b, r, radius);
            g.DrawRoundRect(p, r, radius);
 
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;
 
            g.DrawString("BUTTON", this.Font, b, r, sf);
 
            sf.Dispose();
        }
        public void DrawMyNewRoundRectButton(Graphics g, Pen p, Brush b, RectangleF r, float radius)
        {
            g.FillRoundRect(b, r, radius);
            g.DrawRoundRect(p, r, radius);

            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;

            g.DrawString("BUTTON", this.Font, b, r, sf);

            sf.Dispose();
        }

More PropertyGrid Fun

No Comments

In the past few weeks, I’ve been tasked with creating a new control. This control requires some interesting features that need to “roll-up” within a property grid to get out of the way of the user… but still be there when they need them.

While I can’t post my production code and explain it all, I did think this was worth writing about. It’s not nearly as hard as I expected it to be, and I can see very unique ways to use this kind of feature for programs that already rely heavily on the property grid control.

For the sake of the example, let’s say that our job is to create a field in a control that — when displayed in a property grid — would have four fields that roll up under one field name: Name, Count, Background Color and Foreground Color.

We would first create a class that would house all of our roll-up variables.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public class RollUp
    {
        [DisplayName("Name"), 
        Description("The name of our setting")]
        public string Name { get; set; }
 
        [DisplayName("Count"), 
        Description("How many widgets do we need?")]
        public int Count { get; set; }
 
        [DisplayName("Background Color"),
        Description("Choose a background color for our widget")]
        public Color Background { get; set; }
 
        [DisplayName("Foreground Color"),
        Description("Choose a foreground color for our widget")]
        public Color Foreground { get; set; }
 
        public RollUp()
        {
            Foreground = Color.Black;
            Background = Color.White;
        }
    }
    public class RollUp
    {
        [DisplayName("Name"), 
        Description("The name of our setting")]
        public string Name { get; set; }

        [DisplayName("Count"), 
        Description("How many widgets do we need?")]
        public int Count { get; set; }

        [DisplayName("Background Color"),
        Description("Choose a background color for our widget")]
        public Color Background { get; set; }

        [DisplayName("Foreground Color"),
        Description("Choose a foreground color for our widget")]
        public Color Foreground { get; set; }

        public RollUp()
        {
            Foreground = Color.Black;
            Background = Color.White;
        }
    }

There. That wasn’t too hard. Hopefully all of the above makes sense to you. If not, you may want to read up on PropertyGrids and how they use the DisplayName and Description attributes.

Now, we need to create a class that will tell the PropertyGrid how to handle this class. Hint… we want to make it expandable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    public class ExpandableRollUpConverter : ExpandableObjectConverter
    {
    
        public override object ConvertTo(ITypeDescriptorContext context,
            System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is RollUp)
            {
                return ((RollUp)value).Name;
            }
 
            return base.ConvertTo(context, culture, value, destinationType);
        }
    }
    public class ExpandableRollUpConverter : ExpandableObjectConverter
    {
    
        public override object ConvertTo(ITypeDescriptorContext context,
            System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is RollUp)
            {
                return ((RollUp)value).Name;
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

This class inherits from ExpandableObjectConverter, which tells the property grid to collapse or expand the values in the class. The one method we override tells the property grid now to label the “top” property that everything rolls up under.

The last step would be to apply this attribute to your class so that the property grid knows which class to apply the roll-up to:

1
2
3
4
5
    <b>[TypeConverter(typeof(ExpandableRollUpConverter))]</b>
    public class RollUp
    {
        ...
    }
    <b>[TypeConverter(typeof(ExpandableRollUpConverter))]</b>
    public class RollUp
    {
        ...
    }

There. When you put any object that contains a RollUp property, the property grid should now look like:

Note that you can now roll the “Roller” property up and down as-needed. It is named “TEST” because the name of it is “TEST”. (Duh!) But the “Roller” line is not editable. You have to expand the object to edit any of its contents.

With just a couple minor changes to the ExpandableRollUpConverter class, we can also make the “Roller” line editable to manage the data within without expanding the field.

Within the ExapndableRollupConverter class, tell the PropertyGrid that we CanConvert from a string to one of our native objects.

1
2
3
4
5
6
7
8
9
10
11
    public class ExpandableStepConverter : ExpandableObjectConverter
    {<b>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }
 
            return base.CanConvertFrom(context, sourceType);
        }</b>
    public class ExpandableStepConverter : ExpandableObjectConverter
    {<b>
        public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
        {
            if (sourceType == typeof(string))
            {
                return true;
            }

            return base.CanConvertFrom(context, sourceType);
        }</b>

All this is saying is that we can convert from a string. Otherwise, return what we could convert from previously (which is nothing).

Next, simply handle the string that you convert from. We want to enforce the format “Name (pipe) Qty (pipe) Background Color (pipe) Foreground Color”

With this format, we can split the string up based on the pipe characters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
        public override object ConvertFrom(ITypeDescriptorContext context, 
            System.Globalization.CultureInfo culture, object value)
        {
            if (value is string)
            {
                string[] arr = ((string)value).Split("|".ToCharArray());
                RollUp ret = new RollUp();
                if (arr.Length > 0)
                {
                    ret.Name = arr[0];
                }
                if (arr.Length > 1)
                {
                    try { ret.Count = int.Parse(arr[1]); }
                    catch { }
                }
                if (arr.Length > 2)
                {
                    try { ret.Background = Color.FromName(arr[2]); }
                    catch { }
                }
                if (arr.Length > 3)
                {
                    try { ret.Foreground = Color.FromName(arr[3]); }
                    catch { }
                }
                return ret;
            }
 
            return base.ConvertFrom(context, culture, value);
        }
        public override object ConvertFrom(ITypeDescriptorContext context, 
            System.Globalization.CultureInfo culture, object value)
        {
            if (value is string)
            {
                string[] arr = ((string)value).Split("|".ToCharArray());
                RollUp ret = new RollUp();
                if (arr.Length > 0)
                {
                    ret.Name = arr[0];
                }
                if (arr.Length > 1)
                {
                    try { ret.Count = int.Parse(arr[1]); }
                    catch { }
                }
                if (arr.Length > 2)
                {
                    try { ret.Background = Color.FromName(arr[2]); }
                    catch { }
                }
                if (arr.Length > 3)
                {
                    try { ret.Foreground = Color.FromName(arr[3]); }
                    catch { }
                }
                return ret;
            }

            return base.ConvertFrom(context, culture, value);
        }

And finally, make the “To String” format match the “From String” portion so we can keep everything in sync:

1
2
3
4
5
6
7
8
9
10
11
        public override object ConvertTo(ITypeDescriptorContext context, 
            System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is RollUp)
            {<b>
                RollUp R = (RollUp)value;
                return R.Name + "|" + R.Count + "|" + R.Background.Name + "|" + R.Foreground.Name;
            </b>}
 
            return base.ConvertTo(context, culture, value, destinationType);
        }
        public override object ConvertTo(ITypeDescriptorContext context, 
            System.Globalization.CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is RollUp)
            {<b>
                RollUp R = (RollUp)value;
                return R.Name + "|" + R.Count + "|" + R.Background.Name + "|" + R.Foreground.Name;
            </b>}

            return base.ConvertTo(context, culture, value, destinationType);
        }

And there you have it… you now have an expandable object that can also take string input. You can now enter TEST|4|Blue|Red and the object beneath will change accordingly.

Try it for yourself!

XCode Notes and Thoughts

No Comments

So, I’ve taken the dive into XCode development for iPhone.

I have to say that it’s been a very long time since I’ve worked with C. Not C++. Not C#. Not Sea Shells by the Sea Shore… C. Wow, it’s a huge shift back. I’ve forgotten how much .NET does for me, but this was a brisk reminder. I will say that Objective-C (the variant of C that XCode was designed for) is interesting.

The Good
It is Object Oriented, and well-structured. It took me quite a while to get used to the syntax, but once you wrap your head around it (assuming you already get the idea of pointers and address spaces), it does come together nicely. If you don’t know what a pointer is, or can’t quite fathom how a string is actually a sequence of address spaces, you’ll want to learn ANSI-C before you dive into Objective-C. It’s a whole new trip.

Speaking of strings, I do really enjoy the implementation of NSString and the other NS classes. They seem relatively intuitive and easy to use. NSString takes away all of the pain that old C-style strings used to give me. I will note any newbies to QUICKLY learn the differences between NSMutableString and NSString. That’s probably saved me the greatest headaches.

A quick summary: NSString is a fixed memory space that contains a specific string. Once it is created, it cannot be changed. It can be used, and may even be used to create a new, modified NSString object. But that memory-space itself cannot be changed.

NSMutableString is basically the same concept, but allows new hooks that will allow you to modify the actual memory space as you go. It handles memory management of allocating new space, releasing the existing memory, etc. I haven’t done research into how fast or intensive this process is, but I’ve read good results from other sources.

Memory Managment
The other nightmare of ANSI-C is memory management. Keep in mind that Objective-C does not implement Garbage-collection as a whole like .NET or Java. It does, however, implement a very convenient middle-man idea that works quite well.

Think of it as a “matching-parentheses” concept. The moment you create an object (i.e. alloc) it gets a free “Open Paren”. Starting at it’s creation, you have a paren imbalance. You can then ask to “retain” it, thus giving it another “open paren”.

When you are done with an item, you can issue “release”. In our example in the paragraph above, we would need a grand total of two “release” commands… one to match the alloc, one to match the retain. If all of the parens are balanced, then the memory is released. Otherwise, it is NOT released and will wait around for something else to issue more “release” commands to get things to balance.

Keep in mind, it is STILL UP TO THE DEVELOPER to make sure that all of the “releases” match the “retains” and “alloc.”

This is great because I can create an object, and hand it off to some other outside process to use. If they want to keep it when I’m done they can issue “retain” when they get it. I can then issue my “release” and wipe my hands, but I haven’t destroyed the object for some poor schmuck who is expecting it to stick around until they are done. When they are done, they can issue their own “release” and they can then be assured that if we’ve all done our part in marking it correctly, then no one will end up with an unexpected NULL item.

The Bad
Before you read this section and think that I hate development in XCode, let me set the record straight. It is not my most-favorite experience ever… but it’s not half-bad. Read the following comments with that thought in mind.

The one concept that seems to be employed frequently that I do have an issue with is the Listener/Message system. The concept of tossing a message out into the universe for items to catch is interesting, but seems far too unstructured for my liking. Not that it doesn’t work. It works very well. I’ve written a couple of applications now and I’m getting to understand the ins-and-outs and I understand the necessity of that kind of structure, but it is more a personal quirk. I find it very difficult to document and maintain on a large-scale basis.

I completely forgot how many compiler switches there are to manage in ANSI-C. With the XCode API, most of those are taken care of for you, but you still need to be aware of them in your project details, just in case. I tried writing my first Objective-C app outside of XCode (on a windows PC) and it was less-than successful.

On top of that, XCode 3.x is not NEARLY up to the standards of the development environment that comes standard with the free versions of the .NET languages — specifically the integration between the GUI portion and the code portion.

I’ve heard great reports of XCode 4.x using a new compiler and having much more integrated system, but I’m reserving any comments until I can actually use it on a large-scale basis.

The Very Bad — The Cost
Oh, did I mention that I had to rig up a Mac to write code for the iPhone? Yeah. There are no real good ways around that. I love macs! (really… I do.) I love the interface, and the fact that it is built on a BSD kernel that I can now get in and tweak…. yeah. Geeky-cool awesomeness. I’m an old U*ix hound, so it’s almost like going back home for me.

But the fact that I have to lay out some cash ($99 / year) to just get the “honor” of becoming an Apple developer, then add to that the fact that I have to rig up a junk machine for development (another $500 spent) makes it not my favorite world to play in. I then have to have Apple’s blessing (even though I’ve laid out some cold cash) just for them to THINK about using my application in their App Store… the only way to reach the millions of iPhone users.

At the average “trinket app” price of $1, It will take a large portion of the $99-one-year limit just to get up to speed on development. And then it takes time for your app — once submitted — to be approved into the AppStore. You can reduce that by charging more, but you’d BETTER have a killer app that covers a very specific niche. If you’re creating another Tetris clone or doing something that’s been done before, you’re highly unlikely to sell much if your app is over $1.

So Apple will have $200 (at minimum) before you even see one penny come back from your product, not including any costs you have to create or buy a mac to use as a development machine. Now if you’ve created the next Facebook, that’s chump-change… not even pocket lint. But if you’re creating yet another Tetris clone or something that might scrape a few pennies here or there, you need to take this into consideration. It’s a fairly large investment to just drop on dabbling around.

And it’s fairly complicated to run through all of the hoops that Apple requires JUST to go from development to “sold”. (That’s a whole new post for a whole new day.)

In fact, you could put in all that time and have an Apple rep tell you that you didn’t handle memory properly, thus your wonderful new app will NOT be added to the App Store until you go back in and re-work it. Yep. That’s happened. If the Apple rep encounters something odd, they can call it a “bug” (even though it works fine for you) and deny your app submission. The end.

And when it finally DOES reach the public, you’ll now have to sell around 300 copies of your app just to break even, or more if you had to buy a Mac. And once your $99/year license expires, all of your apps come out of the app store, so you’ll need to CONTINUE to sell around $150 – $200 copies of your apps each year just to continue to break even. And Apple gets their cut on each one of those.

Apple has the sweet deal. You just have “a” deal. Not that I’m complaining… I knew this before getting into the game, and I still agreed to those terms. It’s all on my head. And if you follow suit, all of that will be on YOUR head. Just know all of this before you sign up to write iPhone apps if you expect to make a single penny back.

Summary
On a whole, I give XCode and iPhone app development a 3 out of 5 stars. The IDE is not up-to-par and the official submission process to go from development to cash-in-hand is … well… the word “painful” is still a massive understatement. But the ability to reach millions of users with a niche application is enticing enough to get around many of those barriers.

The language itself is easy to learn once you wrap your head on one or two key topics, and while the XCode IDE is a bit more difficult, it is still quite usable. Just remember that Apple will be SURE to make a profit. You, on the other hand, are not guaranteed any such thing. In fact, it’s probably a money-pit unless you have a clear vision and a business plan to get there so tread with caution.

Printing with a decent preview in C#

3 Comments

Using the PrintPreview dialog in C# can definitely save on paper. You get to see the the entire page before it ever warms up the toner drum. But, what I’ve found is that what you see in the print preview dialog is one thing, but the paper ends up being off by quite a bit.

Well.. actually, the printer is printing exactly what it’s told. But when I tweak and twiddle and get it just right in the print preview, it’ll be off by the time it hits paper. As I’ve discovered, it’s not the printer’s fault. It’s a flaw in the PrintPreview dialog.

Okay Redmond… WHY? If I can figure out how to make your print preview dialog actually preview the correct format, why can’t you write your print preview control to do these steps FOR me? It’s crazy that your print preview doesn’t take this into account.

The problem is due to the fact that PrintPreview doesn’t take the printer driver’s HardMarginX and HardMarginY variables into consideration before plotting the graphics. The solution, … take HardMarginX and Y into account. Duh.

To do this, I have to recognize whether or not the document is being displayed in the dialog or on paper. I’m using the IsPreview flag to manage this for me. I can turn it on just before I warm up the dialog, and have the dialog turn it off at the end of the display. Then, if the user prints from within the dialog, it will sill print properly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
public partial class Form1 : Form
{
    bool IsPreview = false;
 
    public Form1()
    {
        InitializeComponent();
    }
 
    private void button1_Click(object sender, EventArgs e)
    {
        IsPreview = true;
        PrintDocument pd = new PrintDocument();
 
        pd.EndPrint += new PrintEventHandler(pd_EndPrint);
        pd.PrintPage += new PrintPageEventHandler(pd_PrintPage);
 
        PrintPreviewDialog ppd = new PrintPreviewDialog();
 
        ((Form)ppd).WindowState = FormWindowState.Maximized;
 
        ppd.Document = pd;
        ppd.ShowDialog();
    }
 
    void pd_EndPrint(object sender, PrintEventArgs e)
    {
        IsPreview = false;
    }
 
    void pd_PrintPage(object sender, PrintPageEventArgs e)
    {
        Rectangle b3 = e.PageBounds;
 
        if (!IsPreview)
        {
            e.Graphics.TranslateTransform(
                -e.PageSettings.HardMarginX, 
                -e.PageSettings.HardMarginY);
        }
 
        b3.Width -= (int)e.PageSettings.HardMarginX * 2;
        b3.Height -= (int)e.PageSettings.HardMarginY * 3;
 
        int y = b3.Y;
        int x=0;
        while ((y + 25) < b3.Bottom)
        {
            x++;
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;
 
            Rectangle R = new Rectangle(b3.X, y, b3.Width, 25);
 
            e.Graphics.DrawRectangle(Pens.Black, R);
            e.Graphics.DrawString(x.ToString(), this.Font, 
                Brushes.Black, b3.X + 5, y + 5);
 
            y += 25;
        }
        // draw the last little bit
        e.Graphics.DrawRectangle(Pens.Black, 
                new Rectangle(b3.X, y, b3.Width, b3.Height - y));
 
        e.HasMorePages = false;
    }
}
public partial class Form1 : Form
{
    bool IsPreview = false;

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        IsPreview = true;
        PrintDocument pd = new PrintDocument();

        pd.EndPrint += new PrintEventHandler(pd_EndPrint);
        pd.PrintPage += new PrintPageEventHandler(pd_PrintPage);

        PrintPreviewDialog ppd = new PrintPreviewDialog();

        ((Form)ppd).WindowState = FormWindowState.Maximized;

        ppd.Document = pd;
        ppd.ShowDialog();
    }

    void pd_EndPrint(object sender, PrintEventArgs e)
    {
        IsPreview = false;
    }

    void pd_PrintPage(object sender, PrintPageEventArgs e)
    {
        Rectangle b3 = e.PageBounds;

        if (!IsPreview)
        {
            e.Graphics.TranslateTransform(
                -e.PageSettings.HardMarginX, 
                -e.PageSettings.HardMarginY);
        }

        b3.Width -= (int)e.PageSettings.HardMarginX * 2;
        b3.Height -= (int)e.PageSettings.HardMarginY * 3;

        int y = b3.Y;
        int x=0;
        while ((y + 25) < b3.Bottom)
        {
            x++;
            StringFormat sf = new StringFormat();
            sf.Alignment = StringAlignment.Center;

            Rectangle R = new Rectangle(b3.X, y, b3.Width, 25);

            e.Graphics.DrawRectangle(Pens.Black, R);
            e.Graphics.DrawString(x.ToString(), this.Font, 
                Brushes.Black, b3.X + 5, y + 5);

            y += 25;
        }
        // draw the last little bit
        e.Graphics.DrawRectangle(Pens.Black, 
                new Rectangle(b3.X, y, b3.Width, b3.Height - y));

        e.HasMorePages = false;
    }
}

Using this method, I am able to generate exactly the same page in both the preview screen and the actual printed page.

Databound Issues

No Comments

So, I’m working with a fairly large object. It’s not a complex object, there are just a few hundred fields to play with.

The GUI for this object is supposed to be a simple interface into each of the many-hundred fields. So, instead of writing a small segment of code for each textbox, dropdown and button, I decided to use databinding for objects.

It works great. Like a dream. Fast, efficient and once I rig the object to the appropriate field, TA-DA!! easy.

That is… until the user types in their name (doesn’t leave the field) and clicks File->Save.

Sadly, the string in memory remains blank because the text field has not pushed its data back up the databind until the user leaves the field, and menu’s don’t get focus, so when the user clicks “File”, the text field still TECHNICALLY has focus. I know… Crazy.

So, how do we get around this? There are two ways: The hack, and The solution.

The hack is pretty slick, most of the time. =) Simply place the following code in the “on Click” event of your main menubar or toolbar:

1
2
3
4
5
6
private void menuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
// Make sure that all text fields have updated by forcing everything
// to lose focus except this lonely little label.
label44.Focus();
}
private void menuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
// Make sure that all text fields have updated by forcing everything
// to lose focus except this lonely little label.
label44.Focus();
}

As label44 takes focus, the fields update. HOPEFULLY by the time the user navigates to whichever menu item they really wanted, all of the underlying data has been completely updated and is ready for use.

But this is, admittedly, a hack. I mean, I don’t think the good ole-boys in Redmond said “Hey… just focus on some other control from within your menu. Yeah. That’s how we’ll solve this.”

No. It just seems too random, and too uncertain. How do I know if my data is ready for use? How do I know which control USED to have focus, so that I can put focus back? What happens if the user decides to NOT chose a menu item? Some poor little label now has focus, and you can’t continue typing where you left off. What if the user uses the “Alt-F” version and doesn’t use the mouse?

See?? Too random.

UPDATE: 11/16/2010
Now for the ACTUAL solution. Thanks to a great poster on StackOverflow.com, I found that my fields were set to update OnValidate. For 99.99% of the cases, this works. But if you want to update the field, set the Advanced binding type to OnPropertyChanged.

You can do this via the code through a like like the following:

this.textBox1.DataBindings.Add(new System.Windows.Forms.Binding(“Text”, this.someBindingSource, “SomeProperty”, true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged));

Or, you can set it via the Advanced button:

Property Window -> (DataBindings) -> (Advanced) -> Data Source Update Mode

This is the correct fix for the problem.

VSTO with Outlook 2003

No Comments

I’m creating a VSTO plugin for Outlook 2003. My problem? I’m using DevStudio 2008 professional.

It has a project template for Outlook 2003 add-ins, but be warned. It is far from complete.

I was able to build my add-in, but distributing it became a nightmare. A royal, amazingly difficult, pain in the gluteus-maximus.

So, how’d I get it installed? First, I had to make sure that the Visual Studio Tools for Office Runtime was installed on each PC.

  • You can download that here

    Next, I had to make sure that (and THIS is the undocumented part that caused me SERIOUS headache) the Primary Interop Assemblies (PIA) for Office 2003 were installed on each PC.

  • You can find those here.

    Next, I had to copy the DLL(s) to the user’s hard drive and register each one as able to run. Apparently VSTO (understandably so) has a very very tight security policy. You ahve to register EACH dll that you want to use, and any DLLs that THOSE use. Basically, if you copy it, you register it.

    In order to register the DLL’s I used a batch file

    <br />echo Setting Security...<br />REM Turn off prompting for a minute.<br />"C:WindowsMicrosoft.NETFrameworkv2.0.50727caspol.exe" -polchgprompt off</p>
    <p>REM Register each DLL using the FULL PATH to the DLL.<br />REM Repeat this line for each DLL that you want to register. <br />REM I used the same [Project Name] and [Description] for each DLL.<br />"C:WindowsMicrosoft.NETFrameworkv2.0.50727caspol.exe" -u -ag All_Code -url "[Full Path to DLL]" FullTrust -n "[Project Name]" -d "[Description]"</p>
    <p>REM Turn prompting back on. We're done.<br />"C:WindowsMicrosoft.NETFrameworkv2.0.50727caspol.exe" -polchgprompt on<br />

    After setting up all of the applications and security, we now have only one thing left to do… tell Outlook where to find it’s new add-in. You ahve to do this via Registry settings.

    I put everything into HKEY_LOCAL_MACHINE instead of HKEY_CURRENT_USER. It seemed to work, but some people suggest against this on the forums. I prefer it, so use which ever one suits you best.

    You can find the registry keys to set here at the bottom of that page.

    After *ALL* of this, I was finally able to get my add-in off the ground. I hope this sincerely helps someone else.

  • Clean PropertyGrid selectors

    No Comments

    Over the past week, I’ve been working alot with the PropertyGrid control. These controls are great if you have a bunch of wildly-differing or unknown classes. You can feed the class to the PropertyGrid and it allows the user to directly manipulate the object.

    The drawback, many times the field names or values are not user-friendly. In fact… they can be completely ugly.

    The key is being sure that your classes are PropertyGrid-friendly. Each field should specify some compile-time attributes like the ones below:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
            [
             CategoryAttribute("2 - Bottom"),
             DisplayName("Bottom Diameter"),
             Description("The diameter of the bottom attachment.")
            ]
            public virtual double BottomDiameter {
                get
                {
                   ...
            [
             CategoryAttribute("2 - Bottom"),
             DisplayName("Bottom Diameter"),
             Description("The diameter of the bottom attachment.")
            ]
            public virtual double BottomDiameter {
                get
                {
                   ...

    However, what happens when you want to edit something that’s not a primitive like double or string?

    The UGLY way

    I had several fields that could take a string as input, but would parse the string and result in a specific object. I’ve found a very nice, clean, simple solution for this. Before I’d stumbled on this solution, I was using UITypeEditor.

    First, I would have to identify all of the properties that I want to mangle with this UITypeEditor.

    1
    2
    3
    4
    5
    6
    7
    8
    
        [
         EditorAttribute(typeof(ObjectSelector), typeof(UITypeEditor)),
         DisplayName("The Object List")
        ]
        public SomeObject O
        {
            // ... get, set ...
       }
        [
         EditorAttribute(typeof(ObjectSelector), typeof(UITypeEditor)),
         DisplayName("The Object List")
        ]
        public SomeObject O
        {
            // ... get, set ...
       }

    I would create a UITypeEditor that relied on a corresponding form (which was nothing more than a form containing a listbox at 100% width and height.)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    
        class ObjectSelector: UITypeEditor
        {
            IWindowsFormsEditorService editorService = null;
     
            public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
            {
                return UITypeEditorEditStyle.DropDown;
            }
     
            public override object EditValue(ITypeDescriptorContext context,
                IServiceProvider provider, object value)
            {
                // ...
                // Define some variables...
                // ...
     
                if (provider != null)
                {
                    editorService =
                        provider.GetService(
                        typeof(IWindowsFormsEditorService))
                        as IWindowsFormsEditorService;
                }
     
                if (editorService != null)
                {
                    MyDropdownForm f =
                       new MyDropdownForm(
                        (SomeObjectThing)value,
                        editorService);
     
                    // Populate the listbox on the form before displaying it.
                    f.Populate();
     
                    editorService.DropDownControl(f);
     
                    value = f.SelectedObject;
     
                    if (value == null) return null;
     
                    // if the two objects are actually the same object, then return.
                    if (value.Equals(OldValue) || f. SelectedObject == OldValue)
                    {
                        return value;
                    } else {
                       // ...
                       // Populate my new object as-needed.
                       // ...
                    }
                }
     
                return value;
            }
        }
        class ObjectSelector: UITypeEditor
        {
            IWindowsFormsEditorService editorService = null;
    
            public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
            {
                return UITypeEditorEditStyle.DropDown;
            }
    
            public override object EditValue(ITypeDescriptorContext context,
                IServiceProvider provider, object value)
            {
                // ...
                // Define some variables...
                // ...
    
                if (provider != null)
                {
                    editorService =
                        provider.GetService(
                        typeof(IWindowsFormsEditorService))
                        as IWindowsFormsEditorService;
                }
    
                if (editorService != null)
                {
                    MyDropdownForm f =
                       new MyDropdownForm(
                        (SomeObjectThing)value,
                        editorService);
    
                    // Populate the listbox on the form before displaying it.
                    f.Populate();
    
                    editorService.DropDownControl(f);
    
                    value = f.SelectedObject;
    
                    if (value == null) return null;
    
                    // if the two objects are actually the same object, then return.
                    if (value.Equals(OldValue) || f. SelectedObject == OldValue)
                    {
                        return value;
                    } else {
                       // ...
                       // Populate my new object as-needed.
                       // ...
                    }
                }
    
                return value;
            }
        }

    I would then have to include a form MyDropdownForm:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
        public partial class MyDropdownForm : UserControl
        {
            private IWindowsFormsEditorService editorService = null;
     
            public object SelectedObject = null;
     
            public MyDropdownForm (object CurrentValue, IWindowsFormsEditorService EdService)
            {
                InitializeComponent();
     
                editorService = EdService;
                SelectedObject = CurrentValue;
     
                ObjectList.SelectedIndexChanged += new EventHandler(ObjectList_SelectedIndexChanged);
            }
     
            void ObjectList_SelectedIndexChanged(object sender, EventArgs e)
            {
                SelectedSObject = SegmentList.SelectedItem;
     
                this.editorService.CloseDropDown();
            }
     
            internal void Populate()
            {
     
                // ... you get the idea...
     
            }
        }
        public partial class MyDropdownForm : UserControl
        {
            private IWindowsFormsEditorService editorService = null;
    
            public object SelectedObject = null;
    
            public MyDropdownForm (object CurrentValue, IWindowsFormsEditorService EdService)
            {
                InitializeComponent();
    
                editorService = EdService;
                SelectedObject = CurrentValue;
    
                ObjectList.SelectedIndexChanged += new EventHandler(ObjectList_SelectedIndexChanged);
            }
    
            void ObjectList_SelectedIndexChanged(object sender, EventArgs e)
            {
                SelectedSObject = SegmentList.SelectedItem;
    
                this.editorService.CloseDropDown();
            }
    
            internal void Populate()
            {
    
                // ... you get the idea...
    
            }
        }

    This was alot of code. The worst part is, if I wanted to change anything about how it worked, I needed to add two new files to my project. And they were clunky files, at that.

    The CLEAN way

    That is… until I discovered TypeConverter. This simplified things INCREDIBLY. I still had to identify the properties, but the Attribute was much more concise:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
        [
         TypeConverter(typeof(ObjectConverter)),
         DisplayName("The Object List")
        ]
        // NOTE that it is a string. Use the private in the get and set to
        // use the object's StringParse(str) method.
        public string O
        {
            // ... get, set ...
        }
     
        private SomeObject _o = new SomeObject();
        [
         TypeConverter(typeof(ObjectConverter)),
         DisplayName("The Object List")
        ]
        // NOTE that it is a string. Use the private in the get and set to
        // use the object's StringParse(str) method.
        public string O
        {
            // ... get, set ...
        }
    
        private SomeObject _o = new SomeObject();

    The rest just became very easy.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
        internal class SegmentConverter : StringConverter
        {
            public override bool GetStandardValuesSupported (ITypeDescriptorContext context)
            {
                //True - Show the Combobox
                //False - Just take/set the string.
                return true;
            }
     
            public override bool GetStandardValuesExclusive (ITypeDescriptorContext context)
            {
                //False - Editable
                //True - ReadOnly
                return false;
            }
     
            public override StandardValuesCollection GetStandardValues (ITypeDescriptorContext context)
            {
                // Do whatever you have to do here to get your object into a list
                // or array of strings. Luckily, I already had one of those methods
                // in a static Data-Access Object.
                List objNames = daoObject.GetNames();
     
                return new StandardValuesCollection(segNames.ToArray());
            }
        }
        internal class SegmentConverter : StringConverter
        {
            public override bool GetStandardValuesSupported (ITypeDescriptorContext context)
            {
                //True - Show the Combobox
                //False - Just take/set the string.
                return true;
            }
    
            public override bool GetStandardValuesExclusive (ITypeDescriptorContext context)
            {
                //False - Editable
                //True - ReadOnly
                return false;
            }
    
            public override StandardValuesCollection GetStandardValues (ITypeDescriptorContext context)
            {
                // Do whatever you have to do here to get your object into a list
                // or array of strings. Luckily, I already had one of those methods
                // in a static Data-Access Object.
                List objNames = daoObject.GetNames();
    
                return new StandardValuesCollection(segNames.ToArray());
            }
        }

    This is MUCH simpler, faster, easier, and more maintainable. I hope this little review helps someone else to build the best PropertyGrid-enabled objects!

    Older Entries