Showing posts with label winforms. Show all posts
Showing posts with label winforms. Show all posts

Tuesday, December 15, 2009

WPF UserControl IsEnabled in WinForms host

While debugging today, I ran into something rather odd.  Two things actually.  And since I'm falling behind in my posting I thought I'd share.

Here's what I was dealing with.  I have a WinForms UserControl which contains a WPF UserControl within an ElementHost.  The WPF control is being informed of what is selected on the form and is Enabling or Disabling itself accordingly.  However, there is a special case in which the form knows it wants the control to be disabled all the time.

Before I go any further.  If you're working with WPF controls hosted in WinForms and you're dealing with enabling and disabling things, you should be aware of this bug and this workaround on Microsoft Support.  It will cause all kinds of havoc if you set things to Enabled=false before showing them.

The WPF user control is using MVVM, so it's IsEnabled property was bound to an IsEnabled property on the View-Model.  This is where we run into the first interesting thing: this doesn't work.  The vm.IsEnabled property was changed, and the PropertyChanged event was fired with "IsEnabled" as the property name, but the WPF user control's IsEnabled property did not change.  I found someone else who had this same issue and posted about it on this blog.  His work around cracks me up, I can't believe it works...  WPF is crazy.

My work around was to just hook the PropertyChanged event myself like this:
void _vm_PropertyChanged( object sender, PropertyChangedEventArgs e )
{
  if ( e.PropertyName == "IsEnabled" )
    this.IsEnabled = _vm.IsEnabled;
}
I was stunned when this code didn't work. It fired, it set the property, but after setting it the property value didn't change. And it didn't change after WPF got a chance to run through a layout pass either.

This is the second interesting thing.  Turns out the WinForm's control that the WPF control was in was disabled. Why?  Because the parent form had disabled it due to that "special case" I mentioned.  So when I set the WPF user control's IsEnabled to true it didn't matter because it's parent's Enabled property was false. So it just shrugged it's shoulders and ignored me.

So before I can enable the WPF control, I need to enable it's parent, the WinForms control.

In order to get this whole mess working what I ended up doing looks something like this:
  1. View-Model fires change event for IsEnabled
  2. WPF User Control fires custom change event for IsEnabled (it does NOT try to set IsEnabled)
  3. WinForms User Control sets Enabled = CustomEnabled from WPF User Control
  4. WinForms User Control's EnabledChanged fires
  5. WinForms User Control sets WPF UserControl's IsEnabled property = this.Enabled
  6. WPF User Control's IsEnabledChanged fires
  7. WPF User Control sets View-Model's IsEnabled = this.IsEnabled
This ensures that all Enabled properties on all the objects involved here will always be in sync, no matter which one you change.

Part of the problem here I'm going to blame on bad design.  I don't think the control should be being enabled and disabled in two completely different ways (one from the top (WinForms), and one from the bottom (View-Model)).  The rest of the problem I'm going to blame on Enabled properties being really confusing.  They have mystical relationships with their parents and their values can change at different times.  Most properties when you give them a value either have that value after the set, or throw an exception.  But not Enabled!  It's mystical.  I find that I know this but that I still get bit by it when I'm not expecting it.

Monday, June 23, 2008

Custom Drop Downs

For at least the last year I have had an unhealthy obsession with drop downs. Custom drop downs really.

That is, combo boxes. But when you click on the down arrow, you get a list with check boxes. Or a tree control. Or a tree control with check boxes. Or a custom edit surface with a text box and an "Apply" button. Or anything you can possibly imagine, really.

All of these things are immensely useful. And if the story ended there I would never have become so obsessed. Sadly, the story continues. It turns out, such things are not exactly easy to build.

My first attempt involved Subclassing the .NET combo box. I got a working drop down in the end, but it had some odd behavior, and was overall just a huge hack.

Next I learned that Infragistics had a component that you could use to create custom drop downs. Put any control in it you want and you're off to the races. For a time, my obsession was sated. Until we learned that this component could not be resized after it was opened. To resize it, you had to close it and reopen it. This results in noticeable, seizure inducing, flickering. And that = bad.

Later, I learned about how easy this was to do with WPF. In fact, it was the very first thing I ever tried in WPF. But that doesn't help me much at work where our software is all WinForms.

Fortunately, this story has a happy ending! It turns out that .NET 2.0 introduced a component much like the Infragistics one but without the added suck!

It is called the ToolStripDropDown. Basically it's just a Form with special properties that make it behave like a popup. It doesn't steal focus when it opens. It closes as soon a user clicks off it. It can have a shadow border (if you'd like) like a context menu. You can override its default close behavior by using the Closing event and checking the e.CloseReason enumeration value and setting e.Cancel = true. And best of all, it can host any .NET control you want to put in it, as long as you host that control in a TreeStripControlHost.

I stumbled on it at jfo's blog here and immediately put it into use. In the process of trying to do some wacky custom stuff I found the same material from jfo's blog, but on msdn here which you might prefer. Both of those show example code for putting a treeview in a custom drop down.

I must have spent months working on this problem. I probably executed thousands of Google searches looking for solutions. All I ever came across was people using borderless forms and the deactivate event, which doesn't quite cut it for all cases. So when I finally discovered this, my obsession compelled me to post about it.

The only thing that I haven't tried yet is making a "suggest" combo box using this technique. This is a control in which when the user types, you execute a stored procedure and fill the drop down with matching items. The potential problem spot here is that you need focus on the text box so you can type, but you need the drop down to be open. I'm guessing you could pull this off with the ToolStripDropDown though. If anyone knows for sure, please leave a comment!

Wednesday, October 10, 2007

Where is UserControl.Text?

Creating UserControls is a very simple process from a high level. But as usual, the devil is in the details. Recently I was going through many of our controls here at work when I noticed that they were all using the “new” keyword to override the BackColor and Text properties. Somehow we’d managed to get by without this causing any problems until now. To correct this I had to override the OnBackColorChanged and OnTextChanged virtual methods.

After this was done I noticed that the Text property was no longer visible in intellisense for any of our UserControls. After looking closer at the object model I learned that UserControl inherits from Control which contains the Text property. So how come I couldn’t see the Text property? You can’t remove properties through inheritance.

It turns out there is a special designer attribute called System.ComponentModel.EditorBrowsableAttribute. This attribute can be placed on a property or method to determine change the visibility of that property or method in the designer. The possible visibility values (EditorBrowsableState) are Never, Advanced, and Always.

UserControl marks the Text property with EditorBrowsable( EditorBrowsableState.Never ).

I have no idea why Microsoft did this. I imagine there is some strange way of thinking which makes it make sense, but I haven’t figured it out yet.

To work around this, I had to override the Text property on a base class that all of our UserControls inherit from. Fortunately we already had a base class, otherwise I would have been forced to create one just so we could use the Text property correctly.

And for the record, yes, the Text property can be overridden. Why was it using the “new” keyword in the first place you ask? I don’t know. I think because whoever wrote the controls just didn’t know it was a virtual property.

Tuesday, August 28, 2007

Memory Leaks in ShowDialog

In an earlier post I talked about Memory Leaks in .NET due to events. This time I'm going to talk about memory leaks that may occur in the use of Form.ShowDialog().

I'm talking about a WinForms Form here, so this doesn't apply to WPF or GTK or any other tool kit (which is not to say they don't have the same behavior, just that I don't know).

When you open a Form with Form.Show() it gets disposed automatically when the user closes it or when you programmatically call the Close() method. However, when you open a Form with Form.ShowDialog() it doesn't get disposed when the user closes it, nor when you programmatically close it. It just gets hidden. Presumably this is so you could re-show it if you wanted to. The downside of this is you have to dispose it manually.

You may be thinking that this isn't a big deal because the garbage collector will take care of it for you when the variable goes out of scope. This is not the case. If you don't call the Dispose method that form, and anything referenced by the form, will remain in memory until your program ends.

Because of this, my best practice recommendation is anytime you're opening a Form with ShowDialog, consider doing some like this:
using( SomeDialog sd = new SomeDialog() )
{
sd.ShowDialog();
}

If you can't constrain the scope of your form to just one method then you're stuck remembering to dispose it before you finish with it.

This behavior is, at least to me, somewhat unexpected. However, it is fully documented in MSDN:
When a form is displayed as a modal dialog box, clicking the Close button (the button with an X at the upper-right corner of the form) causes the form to be hidden and the DialogResult property to be set to DialogResult.Cancel. Unlike modeless forms, the Close method is not called by the .NET Framework when the user clicks the close form button of a dialog box or sets the value of the DialogResult property. Instead the form is hidden and can be shown again without creating a new instance of the dialog box. Because a form displayed as a dialog box is not closed, you must call the Dispose method of the form when the form is no longer needed by your application.

Tuesday, August 14, 2007

Fun With Circles



I recently decided that I wanted to create a "Progress Spinner." That's a stupid name for a control that looks like its spinning around to indicate that something is happening in the background. In other words, its a progress bar without the bar. The picture shows my end result (I was too lazy to try to make an animated gif).

I decided I wanted this because I hate progress bars that just go back and forth endlessly. Progress bars should be reserved for times when you can actually estimate how far along in a process you are as a discrete percentage. When you have no idea how far along you are, then just display a "spinny thingy" (an alternate name).

Progress spinners are pretty popular today. You see them in Micrsoft software like the Reporting Services WinForms viewer, or Apple's iPod, or the new iPhone. They're everywhere.

Anyway, I thought it would be a fun little side project to create one. Mostly because I would have to re-teach myself some trigonometry, all of which I had forgotten.

Here's the details: Display x number of circles (with r radii) in a circle which fills the size of the user control. Use two colors, a base color and an active color, which can be changed by the user. The active color appears to move clockwise around the outer circle. Allow the speed of the rotation to be changed.

The math was the fun part. When I started, I had no idea how to calculate the location of each of the circles (henceforth referred to as markers). It turns out its quite easy though, just use Polar coordinates and convert to Cartesian coordinates.

In case you can't remember, just like I couldn't, Polar is given by the distance r from the center to the point and the angle theta from what would usually be the positive x axis to the point. In my case, r is constant so I only need to calculate theta for each marker. 2 * PI / num markers gives the angle between each marker. So just loop for each marker and increment theta by that amount.

So that was fun and easy. Of course, it couldn't remain that simple... You also have to account for the radius of the markers in r so your markers don't leave the visible space of the control. And you have to convert from Cartesian coordinates to Computer coordinates (cause 0,0 is the top left in a computer, not the center). And finally, the biggest annoyance: account for borders on the WinForms UserControl.

If you have BorderStyle set to anything other than none, all kinds of weird things starts happening. It turns out that if you draw a line from (0,0) to (0, width) ( top-left to top-right) it will be visible. But if you draw a line from (0, height-1) to (width-1, height-1) (bottom-left to bottom-right) it will not be visible. To make that line visible you actually have to use (0, height-3) to (width-3, height-3). The borders take up 2 pixels, even when they're not using them as in BorderStyle.Single. The borders also only steal space at the bottom and right of the control, not the left or top. It makes no sense to me what-so-ever.

Anyway, it was a fun little project. While I was at it, I added the ability to draw the markers as lines instead of circles:

I also added the ability to register a paint event and paint your own markers however you please.

The control also includes the concept of color profiles allowing you to select from green, black, blue, or red so you don't always have to figure out your own colors.

Just for kicks I also added a feature so that as you resize the control, not only does the diameter of the main circle grow, but the diameter of the markers grows proportionally as well.

Finally I got to use a really nice trick with IEnumerable. I created a MarkerPositionManager : IEnumerable class. This allows me to write code like this in the OnPaint method:

foreach( MarkerPosition mp in markerPositionManager )
{
// draw ellipse in mp.BoundingRect
}


I like that because it completely abstracts the logic for positioning the markers from the logic for drawing the markers. I love using this pattern because its very effective and easy to refactor to.

The end result is pretty fun to play with.

Wednesday, August 1, 2007

Is WinForms DataBinding currently in progress?

I've recently been looking into WinForms DataBinding. We have an in house databinding solution that we use, but it is vary narrow in scope, so I wanted to learn how WinForms does it.

I started with an example where I have a very simple user control with 5 or 6 fields (combo boxes, text boxes). I wanted to bind these fields to data from a .NET class which has properties which exactly match the fields I'm displaying. Using WinForms databinding to accomplish this was startlingly easy. But then I hit a snag.

I've registered the ValueChanged event on all my controls (These are in house controls which implement an interface so they all have a ValueChanged. For the most part, all it does is forward the TextChanged and SelectedValueChanged events) and hooked it up to a single event handler. When this event handler fires I enable a "reset" link. Thus if the user changes any of the values, they can click reset to put the values back as they were.

The snag is that when the data binding initially loads the controls, the value changed events fire and I enable my reset link. But when its the data binding that has caused my value changed event to fire, I don't want to enabled the reset link. In other words, I need to know if the data binding caused the value changed or if a user editing the field caused the value changed.

Ideally, I'd like to be able to write something like this:

void Fields_ValueChanged( object sender, EventArgs e )
{
if ( !bindingSource.IsUpdatingControls )
resetLink.Enabled = true;
}


Unfortunately for me, nothing like this seems to exist. And even more infuriating, I can't find anyone else complaining about this on the web...

Now from what I've read I understand that if you're using data binding you're not supposed to depend on the controls. Instead you're supposed to use the object that contains your data. This is for MVC purposes apparently. This means: don't use the control's ValueChanged event. But this doesn't work in my case for 3 reasons: 1) I don't need two way binding here, so my underlying data object shouldn't even get updated. 2) To use two way binding I'd have to change the update mode to OnPropertyChanged instead of OnValidation, which I just don't want to have to do. 3) From an MVC perspective, this is View logic which should remain in the View layer and not be dependent on what update mode the underlying data binding is using.

Am I missing something huge here? Is there any way to do this? Or do I have to abandon WinForms data binding? Seems like such a silly thing to force me to write the binding code manually.