XMLpitstop.com   |  VBnetexpert.com   |  Community Credit  
 
 
Pitstop Search:  
in
 
Sign in | Join | Help
 
 
  Blog
    Home  
 
  Entries By Date
 
<November 2008>
SunMonTueWedThuFriSat
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456
 
 
  Blog Categories
   
 
  Archives
    December 2008 (1)  
    November 2008 (3)  
    September 2008 (3)  
    August 2008 (3)  
    June 2008 (4)  
    May 2008 (2)  
    April 2008 (3)  
    March 2008 (3)  
    February 2008 (5)  
    December 2007 (4)  
    November 2007 (1)  
    October 2007 (3)  
 
  Syndication
    RSS  
    Atom  
    Comments RSS  

November 2008 - Posts

  .NET Flea Market  
 

Your Code Is Very Important To Us, Please Continue To Wait...

It was asked of me once: Why isn't my "please wait" form doing anything?  The form had an animation, but the animation never moved.  The main form code would show the "please wait" form, run some code, then hide the "please wait" form.  As it turned out, the "please wait" form was being blocked by a database call and some other stuff in the main form.  There was never a chance for a DoEvents or redraw or anything.

The quick answer is, you have to show the other form in another thread, so it can operate freely and redraw itself.  The longer answer is, you have to deal with threading.

Threading is very easy in VB.NET, but some people want answers, not lessons, so I ended up writing this quick class to show a form in another thread.  It's pretty simple.  You instantiate the class by passing in an instance of your "please wait" form , then call show(), do your work, then call hide().  Like so:

Dim f as New BackgroundForm(new frmWait)

f.Show()
' Do some stuff
f.Hide()

Only one caveat: you can't have the variable for the BackgroundForm class at the class level.  It must be declared, instantiated, and used in the same method.  Otherwise, you will get Cross-Threading errors.

So, here's the class you can use for yourself, if you prefer answers to lessons.

 
Public Class BackgroundForm
    Dim displayForm As Form
    Dim t As System.Threading.Thread
    Dim isActive As Boolean
    Shared lockObject as New Object

    Public Sub New(ByVal f As Form)
        displayForm = f
    End Sub

    Public Sub Show()
        t = New System.Threading.Thread(AddressOf ShowForm)
        t.IsBackground = True
        t.Start()
        isActive = True

    End Sub

    Public Sub Hide()
        isActive = False
    End Sub

    Private Sub ShowForm()
        displayForm.Show()

        Do While isActive AndAlso displayForm.Visible
            System.Threading.Thread.Sleep(100)
            SyncLock lockObject
            Application.DoEvents()
            End SyncLock
        Loop

    End Sub

End Class

I made a slight change to the code to protect against thread stomping.  If you have an animated GIF in your "please wait" form, and you have a more than one "please wait" form active at once, there is the chance you will get a "object is in use elsewhere" error.  This happens when the image for the current form is being changed - in the DoEvents - but another form is being closed/disposed, taking the same image with it.

By wrapping the DoEvents in a SyncLock that is shared among all instances, this is prevented.  I couldn't get the error to happen unless I opened over 20 windows and had them closing at scheduled times, but your luck may be worse.

 
 
 
 

Autosize. No, not the control, the text.

It's nice that .NET controls have an auto-size property so you don't have to worry about overflow and all.  But what about cases where you have a fixed layout?  Well, that's simple, you turn autosize off and fix the control to the size you need.

That's half the story.  What about the text that's inside it?  Now you know I'm talking to marketing people when I say that there are times you want the text to be as big as possible within that control.  But you can't just set the font to a huge size, because sometimes you'll have more text to display and the font size must sadly be reduced.

To accommodate this, I made a quick method that brute-forces the correct font size in the control.  basically, stepping down the size of the font until it fits.  I know loops like this are cheap, poor programming, and I did give consideration to doing some hard math to calculate the proper font size based on the initial size, but sometimes not getting hung up on performance can be liberating.

    Private Sub ResizeText(ByVal c As Control)
        Dim currentSize As Size
        Dim currentFont As Font

        currentFont = c.Font

        Do
            currentSize = TextRenderer.MeasureText(c.Text, currentFont, _
                c.Size, TextFormatFlags.WordBreak)

            If currentSize.Width > (c.Width - c.Margin.Horizontal) _
                OrElse currentSize.Height > (c.Height - c.Margin.Vertical) Then

                currentFont = New Font(currentFont.FontFamily, _
                    CSng(currentFont.Size - 0.5), currentFont.Style, currentFont.Unit)
            Else
                Exit Do

            End If

        Loop While currentFont.Size >= 1

        c.Font = currentFont

    End Sub
 
 
 
 

Casting Upwards

When binding business objects to a datagrid, often you have a need to display some information that is not directly exposed by the object itself.  Maybe it's a calculated value, maybe it's something nested deeper in the object.  When faced with this issue, there are a few different action paths you can take.  You can add extra read-only properties to your business object to support the extra view information.  You can create a new class that inherits from the class you are displaying and put the extra properties in there.  Or you can handle the CellFormatting event in the datagrid and change the displayed values manually.  One of the downsides of using a new derived class with extra properties is that you can't cast a base class to it.  You could cast down to the base class, but no casting up.

Here is a technique that is closest to the second option listed above and side-steps the upcasting problem.  I dislike the first option because it clutters the business object with UI-specific code.  Going with option 2 is only slightly better, while you can populate the correct display-specific object and return it from your business logic layer, either you have to have a method that return the derived type, or you will have to cast it to its correct type in the UI.  Even then, your business layer still contains UI logic.

So, keeping things separated, the derived display-specific class should be defined in the UI layer.  This means it will have extra read-only properties for use with databinding.  The business layer will return the basic object(s), so it will be up to us to convert these to UI-friendly versions.  There are two problems with converting the object: not all the object state may be exposed via public properties, and those properties may contain logic.  It would be best to copy the object by its internal state - private variables.

On first thought, working with the private variables means the code must be inside the source object and the destination object.  This would be tedious to do, passing in the destination object, then sending the source object's private variables to the destination so the destination object can manipulate its own private variables.  Yuck.  However, using Reflection, the job gets a whole lot easier.

Here's a small class with a method to convert one class to another by copying its private and public fields.  The properties are intentionally excluded since they may contain logic that modifies the internal state.  You should use this technique with care and know exactly what it does and does not do.  Basically, it copies values from one instance of a class to another.  This is fine for simple classes, but it's not going to resolve references for you.

Consider ClassA with a private field of type ClassB.  ClassB maintains a private variable with a reference to ClassA, so that it can manipulate all of its "parent's" state and logic.  If you use this technique to cast ClassA to ClassAA, because you want an extra property to display some info from ClassB, you're in for some fun results if you change some data in ClassAA.  This is because ClassB still has a reference to ClassA, not ClassAA.

Public Class UpCaster
    Shared Sub CastUp(ByVal sourceObj As Object, ByVal destinationObj As Object)
        Dim values As New Dictionary(Of String, Object)
        Dim props() As Reflection.FieldInfo 

        props = sourceObj.GetType.GetFields(Reflection.BindingFlags.NonPublic _
            Or Reflection.BindingFlags.Static _
            Or Reflection.BindingFlags.Instance _
            Or Reflection.BindingFlags.Public) 

        For Each p As Reflection.FieldInfo In props
            values.Add(p.Name, p.GetValue(sourceObj))
        Next 

        props = destinationObj.GetType.GetFields(Reflection.BindingFlags.NonPublic _
            Or Reflection.BindingFlags.Static _
            Or Reflection.BindingFlags.Instance _
            Or Reflection.BindingFlags.Public) 

        For Each p As Reflection.FieldInfo In props
            If values.ContainsKey(p.Name) Then p.SetValue(destinationObj, values(p.Name))
        Next 

    End Sub 

End Class
 
 
 

 
Copyright © . All Rights Reserved.
Powered by Community Server (Commercial Edition), by Telligent Systems