XTab's Blog

Ged Mead's Blog at vbCity

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

AprMay 2008Jun
SMTWTFS
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

Archives

Topics

Ramblings

VB.NET

  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 on Friday, February 01, 2008 1:31 PM

Feedback

No comments posted yet.

Post Feedback

Title:
Name:
Url:
Comments: 
Protected by Clearscreen.SharpHIPEnter the code you see: