XTab's Blog

Ged Mead's Blog at vbCity

This blog hosted by:
http://blogs.vbcity.com      
  Home :: Syndication  :: Login

JanFebruary 2008Mar
SMTWTFS
272829303112
3456789
10111213141516
17181920212223
2425262728291
2345678

Archives

Topics

Ramblings

VB.NET

Friday, February 01, 2008 #

  Quite often as I browse through the VB.NET Forums on vbCity I see questions about tasks that can be quite difficult in WinForms, but would be much easier to deal with using WPF. One recent question that fits this description is the following one:

"I am wondering how I can change a specific line of text in a listbox based on a condition.

For example, if I subract listbox.items.item(1) from listbox.items.item(0) and the posted result in position (2) is a negative decimal then make item(2) red.. or if the result is greater than 1000 then turn it green.

I tried some of the examples in some other posts, but they aren't what I am looking for.

Any help would be appreciated.

  As you can see, essentially this is a task where a ListBox contains some items that represent numeric values. Some simple arithmetic is used to calculate the value for the third line. The color of the text on that line is decided depending on whether the value is negative or higher than 1000.

  In Windows Forms this job would need the use of OwnerDraw in the ListBox, with each line being drawn using the GDI+ DrawString method. While not that daunting a task, OwnerDraw and DrawString can get a bit unwieldy if you have a lot of data to handle. So, even though OwnerDraw would probably be fine for this particular example, I still thought it was worth seeing how WPF might be used to deal with it.

  With my WPF head on, I first thought that I would tackle this with a value converter using IValueConverter, reading the values and setting the Brush color based on the value. But as it turns out, WPF's innate ability to accept varying settings for each individual ListBoxItem's Foreground property made it a much easier proposition.

The Basics

  Because I'm concentrating on the UI here, not the mechanics of how to get the values, I've short-circuited this part and simply hard-coded the values into the ListBox. (Don't worry, I'll get to how you can take values from the user, insert them into the ListBox and do the arithmetic at the end, if that's something you need.)

  Here's the Xaml to create the ListBox and those three items:

Code Copy
<Window x:Class="BasicTask"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="BasicTask" Height="300" Width="300">
    <Grid>
        <ListBox Margin="79,38,79,65" Name="ListBox1" FontSize="20">
            <ListBoxItem Name="Value1">2000</ListBoxItem>
            <ListBoxItem Name="Value2">3000</ListBoxItem>
            <ListBoxItem Name="Value3">-1000</ListBoxItem>
        </ListBox>
    </Grid>
</
Window>

  (I've manually done the arithmetic which is very unrealistic, but keeps the code to a minimum for now)

  Of the various ways of tackling this, I decided to use a simple Select Case block in the Code-Behind. The ListBox control has a LayoutUpdated event, which fires whenever there is a change to the layout of the visual elements of the control. We can use this event to apply the appropriate colors to the text items.

Code Copy
Partial Public Class BasicTask

    Private Sub ListBox1_LayoutUpdated(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.LayoutUpdated
        Select Case CDbl(Value3.Content)
            Case Is < 0
                Value3.Foreground = Brushes.Red
            Case Is > 1000
                Value3.Foreground = Brushes.DarkSeaGreen
            Case Else
                Value3.Foreground = Brushes.Black
        End Select
    End Sub
End
Class

  

   So if you already have a ListBox with values in it, that's all the code you need. It's quicker and cleaner than the OwnerDraw route, I think you'll agree.

Taking User Input

  As hard-coding those values in the Xaml isn't very realistic, as promised earlier, here is one way to create the ListBox in Xaml and update it in code:

Code Copy
<Grid>
        <Button Height="23" HorizontalAlignment="Left" Margin="22,91,0,0" Name="Button1" VerticalAlignment="Top" Width="75">Enter</Button>
        <TextBox Height="23" Margin="22,34,0,0" Name="TextBox1" VerticalAlignment="Top" HorizontalAlignment="Left" Width="84">2500</TextBox>
        <TextBox Height="23" Margin="0,34,31,0" Name="TextBox2" VerticalAlignment="Top" HorizontalAlignment="Right" Width="90">900</TextBox>
        <Label Height="28" HorizontalAlignment="Left" Margin="22,0,0,0" Name="Label1" VerticalAlignment="Top" Width="96">First Value</Label>
        <Label Height="28" HorizontalAlignment="Right" Margin="0,0,25,0" Name="Label2" VerticalAlignment="Top" Width="96">Second Value</Label>

        <ListBox Margin="0,91,31,33" Name="ListBox1" FontSize="20" HorizontalAlignment="Right" Width="120">
            <ListBoxItem Name="Value1">0</ListBoxItem>
            <ListBoxItem Name="Value2">0</ListBoxItem>
            <ListBoxItem Name="Value3">0</ListBoxItem>
        </ListBox>
</Grid>

Code Copy
    Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
        Value1.Content = TextBox1.Text
        Value2.Content = TextBox2.Text
        Value3.Content = (CDbl(TextBox1.Text) - CDbl(TextBox2.Text)).ToString
    End Sub
    Private Sub ListBox1_LayoutUpdated(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.LayoutUpdated
        Select Case CDbl(Value3.Content)
            Case Is < 0
                Value3.Foreground = Brushes.Red
            Case Is > 1000
                Value3.Foreground = Brushes.DarkSeaGreen
            Case Else
                Value3.Foreground = Brushes.Black
        End Select
    End Sub

  The result will look something like this

  As you change the values in the two TextBoxes, the ListBox will update and the text color of the third item will reflect the rules in the LayoutUpdated event.

  Because the scenario has a preset configuration of just those three items, the above approach is the easiest.

Varying Number of ListBoxItems

  If for some reason you can't or don't want to create the three ListBoxItems in Xaml, then you can easily (if slightly more verbosely) do this in the code-behind:

Code Copy
Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
        ListBox1.Items.Clear()

        Dim lbi As New ListBoxItem
        lbi.Content = TextBox1.Text
        ListBox1.Items.Add(lbi)

        lbi = New ListBoxItem
        lbi.Content = TextBox2.Text
        ListBox1.Items.Add(lbi)

        lbi = New ListBoxItem
        lbi.Content = (CDbl(TextBox1.Text) - CDbl(TextBox2.Text)).ToString
        ListBox1.Items.Add(lbi)

        Dim item3 As New ListBoxItem
        item3 = CType(ListBox1.Items(2), ListBoxItem)

        Select Case CDbl(item3.Content)
            Case Is < 0
                item3.Foreground = Brushes.Red
            Case Is > 1000
                item3.Foreground = Brushes.DarkSeaGreen
            Case Else
                item3.Foreground = Brushes.Black
        End Select

    End Sub

  You can of course vary the number of ListBoxItems added in this way and select another of them for the text color formatting treatment. You'll see that I put all the code in the Button click this time. If you're wondering why, it's because the LayoutUpdated event fires when the ListBox is first created. As there are no ListBoxItems in existence at that moment, you will get an exception when you try and access the non-existent ListBox1.Items(2) item.

And Finally ....

  Although it's not part of the original question, you might at some time want to comb through a ListBox like this and change the color of all the lines that meet our criteria of less than zero or more than 1000. Enumerating through the ListBoxItems will do this job for you:

Code Copy
  Private Sub ListBox1_LayoutUpdated(ByVal sender As Object, ByVal e As System.EventArgs) Handles ListBox1.LayoutUpdated
        For Each l As ListBoxItem In Me.ListBox1.Items
            Select Case CDbl(l.Content)
                Case Is < 0
                    l.Foreground = Brushes.Red
                Case Is > 1000
                    l.Foreground = Brushes.DarkSeaGreen
                Case Else
                    l.Foreground = Brushes.Black
            End Select
        Next
    End Sub

  And as a final thought, if you only want to turn text Red if the value is below zero and leave it as Black for all other values then you can replace that multi line Select Case block with a single line IIF statement.

  For completeness, all the above samples should have some validation added to ensure that the items exist and that the text represents valid numeric values.

posted @ 1:31 PM | Feedback (0)

First Chance Exceptions

  I suspect that most of us have become so used to seeing the message that reads:-

" A first chance exception of type 'System.ApplicationException' occurred in WindowsApplication1.exe "

 that it hardly hits our conscious thoughts any more.

  But did you ever check out just what this message really means or wonder whether you should or could do anything about it ?

  I suppose the first thing you need to know in order to answer those questions is just exactly what a First Chance Exception is. Well, in it's simplest terms it's a mechanism that flags up that there is a possible error condition. The key point here though is that this flagging up takes place in the Debugger and not in the running application itself. (We probably often think of the two as being the same thing, but in fact that's not really the case).

  When the Debugger detects an Exception situation it raises a First Chance Exception. In several situations it isn't always desirable for the program to come to a grinding halt each time. And of course, being a skilled developer, you will have built appropriate Try Catch exception handling blocks into the code which will sort the problem anyway! So, depending on the Debugging settings you have laid down (more on this later), the Debugger allows this First Chance Exception to pass and the program continues to run.

Second Chance Exceptions

   At this point, where the running program now has to deal with the Exception situation, the application will either handle the Exception or not handle it (your Structured Exception Handling code coming in to play here). If it does handle it, then the application will of course be allowed to proceed and it will happily keep running until the next problem is encountered or the application ends.

  If the program fails to deal with the Exception at this point, the Debugger is notified again of this unhandled error situation - and this is now the Second Chance Exception level. Hitting this point will result in the kind of Unhandled Exception Message Box that I know you will have seen many, many times:

An Exceptional Situation - XamlParseException

  Although I'd never really given it much thought I guess I'd always assumed that by default the settings in Visual Studio were such that all First Chance Exceptions were allowed through to the program as described above. With some timely assistance from the Microsoft Visual Basic team, I discovered recently that this is not the case. There is one Exception which by default is set to Throw on a First Chance Exception - and that is the XamlParseException.

   When I say "by default" I'll have to qualify that slightly. I'm not sure that it is set by default in every kind of installation of Visual Studio 2008, but certainly in the Pro and Express Editions if you have set the Profile to Visual Basic developer settings then this will be the case.

  As a result I spent quite a long time struggling with a particular project that had to deal with many situations where the Xaml at certain points in time was not valid and so would not parse. Even though I had built in a Try Catch block for this Exception, the program would never reach that exception handler because the Debugger would throw the Exception at the first chance stage.

  The route to finding this setting in the IDE is as follows:

Select Debug > Exceptions
then in the Window that appears, choose "Common language Runtime Exceptions" and expand the list.
Navigate down the list until you reach "System.Windows.Markup".
Next click on the plus symbol to show the Exceptions in this class.

  (Alternatively you can click the "Find..." button and enter the name of the Exception).

  You can see in the screenshot below that the "Thrown" checkbox is checked:

 which is of course the reason why my project was stopping in Debug mode whenever it first hit a XamlParseException.

  If you want your code to be allowed to handle this kind of Exception (as I did) then of course you just need to uncheck that box.

  I did a quick sweep of all the settings for all Exceptions and that XamlParseException seems to be the only one that is checked by default. But if you should come across a similar puzzling situation yourself in the future, you should probably think of those Exception settings as one of the places you should first look.

posted @ 11:44 AM | Feedback (0)