XTab's Blog

Ged Mead's Blog at vbCity

vbCity Blogs moved to:
http://cs.vbcity.com/blogs
  Home :: Syndication  :: Login

MayJune 2009Jul
SMTWTFS
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

Archives

Topics

Ramblings

VB.NET

Tuesday, June 09, 2009 #

  I've always found ListViews quite fascinating. Slightly confusing sometimes, but fascinating nevertheless.

  As I have often been heard to say, it's the little Gotchas that'll get ya.     Take, for instance, the subject of this blog - copying an item between ListViews. The scenario is that you let the user click on an item in ListView1 and if they want this item copied to ListView2, they hit a button.

Now, you would probably think that all you need to do is identify the currently selected item and add it straight to the second ListView. Something like:

 Code Copy

ListView2.Items.Add(ListView1.SelectedItems(0))

  But if you do try this, you will get an error.

 

  

  The text of the error message pretty much says it all. What you have to do (assuming that you aren't prepared to remove the item from the original ListView) is to clone it. You will then be allowed to add the clone to the second ListView.

  Although the cloning isn't difficult, you do have to be aware of the need to cast the selected item to ListViewItem if you have Option Strict On. To be honest, I found this a bit strange at first. If I lift a ListViewItem from a ListView, I didn't expect to have to cast it to what it is - i.e a ListViewItem.

  I'm not entirely sure why this occurs and wonder if the underlying reason for this is that the ListViewItem is stored in the SelectedItems collection of the ListView as a generic object. Anyway, Casting it back to a ListViewItem at the point where the cloning takes place, fixes this without any problem.

  This code works well:

Code Copy
If ListView1.SelectedItems.Count > 0 Then
    Dim lvi As New ListViewItem
    lvi = ListView1.SelectedItems(0)
    Dim lvi2 As New ListViewItem
    lvi2 = CType(lvi.Clone, ListViewItem)
    ListView2.Items.Add(lvi2)
End If

  You'll have noticed that I built in a test to ensure that an item is currently selected. It's an easy thing to forget and is sure to bring your app to a grinding halt before long if you don't build this in.

  You can see where I have cast the selected item (aka lvi) to ListViewItem in Line 5. Intriguingly, casting to ListViewItem in the third line of code, e.g.

  Code Copy

lvi = CType(ListView1.SelectedItems(0), ListViewItem)

   doesn't cut it.

   

posted @ 9:44 AM | Feedback (2)

  Sometimes you don't have control over how the data is saved to a text file. For instance, some items might be saved with quotation marks around words or phrases. If you want to read the file but not show these marks then you'll need a way to remove them.

  Like a lot of things, it's actually very easy when you know how. You can use the built-in Replace function of the String class, replacing the marks as you find them. The trick though (and to my mind, the less than totally intuitive bit) is knowing how many quotation marks to use in the first argument of the Replace method's parameters. This is the 'OldChar' parameter, i.e the one you want to replace.

  You would think, wouldn't you, that you could put a Quotation Mark inside a pair of Quotation Marks like this:-

 

Code Copy
MyString.Replace(""", "")

But if you try that, you will find that it doesn't work. What you actually have to do is include a second Quotation Mark inside the outside ones. In other words, you need four Quotation Marks in a row.

 

Code Copy
MyString.Replace("""", "")

  It's only a tiny change, but it will move your mental state from annoyed confusion to enlightened contentment. Or something like that, anyway.

  So putting this together with code that reads from a file and displays the result (minus Quotation Marks) in a ListBox, you have:

Code Copy
Private Sub RemoveQuotes(ByVal filename As String, ByVal target As ListBox)
        '  A StreamReader to fetch the data
        Dim sr As New IO.StreamReader(filename)
        '  A string to hold each line as it is read
        Dim line As String = String.Empty

        ' Read from the file
        ' As long as there is something left to read
        Do While sr.Peek <> -1
            ' Replace the Quotation Marks with Nothing
            line = sr.ReadLine.Replace("""", "")

            ' Add edited text to a ListBox
            target.Items.Add(line)
        Loop

        '  Tidy up when finished
        sr.Close()
        sr = Nothing
End
Sub

  If you prefer your code to be broken down into clearer steps, you could do this instead:

 

Code Copy
    Private Sub RemoveQuotes(ByVal filename As String, ByVal target As ListBox)
        '  A StreamReader to fetch the data
        Dim sr As New IO.StreamReader(filename)
        '  A string to hold each line as it is read
        Dim line As String = String.Empty

        ' Read from the file
        ' As long as there is something left to read
        Do While sr.Peek <> -1
            '  Read the next line
            line = sr.ReadLine

            ' Replace the Quotation Marks with Nothing
        line = line.Replace("""", "")

            ' Add edited text to a ListBox
            target.Items.Add(line)
        Loop

        '  Tidy up when finished
        sr.Close()
        sr = Nothing
    End Sub

 

  Either way, your quotation marks will be history.

posted @ 9:35 AM | Feedback (0)

  One of the things that first caught me out in WPF was the simple topic of colors. For example, let's say you want to reset the BackColor of a Form in Windows Forms.

Easy enough. This will do the job:

Code Copy

Me.BackColor = Color.CadetBlue

  When it comes to WPF, you'll know that we are dealing with a Window, instead of a Form and have probably already picked up that BackColor is now Background.  You can however, still use "Me" to reference the Window.

 But if you were to try something like:

 Code Copy

Me.Background = Color.CadetBlue
    ' or even
Me.Background = Colors.CadetBlue

 you would be disappointed.

 You would however get some help from Intellisense (at least with the second version). The error message tells you that a Color cannot be converted to a Brush. And there's the answer to the problem.

The Background property doesn't take a Color - it takes a Brush, which of course can, and usually does, have a color assigned to it. Don't forget though that you are not limited to a single solid color; there are many gradient, tile and image based options that you can choose when it comes to brushes in WPF.

 So this code will work fine in WPF:

 Code Copy

  Me.Background = New SolidColorBrush((Colors.CadetBlue))

 Ah yes, I hear you say, but what about the theory that you should use XAML for the look and code-behind for the behaviour? Well, I can't disagree with you there and personally I would use:

<Window x:Class="Window1"

    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="300" 
Background="CadetBlue">

  where the Background property for the Window is set there in the XAML. However, there may well be times when you want the user to have a say in color choices and in those cases it can be easier to take the user's input and deal with it in the code-behind.

  For example, if the user was empowered to enter values for the ARGB components then you might use an approach like the following:

 Code Copy

        Dim col As New System.Windows.Media.Color
        '  In reality the values below could be
        ' selected by the user and passed in
        col = Color.FromArgb(214, 122, 52, 24)
        Dim br As New SolidColorBrush(col)
        Me.Background = br

      It would also be quite easy to create a display in WPF where you bind, for example, sliders to the Brush that is used for the background. But I won't go any deeper into that just now, as this sub-set of blog items is meant only to help identify those missing WinForms favorites and repatriate them as WPF troops.

posted @ 9:33 AM | Feedback (0)

  This is a question that seems to come up a lot in the forums:- How can I restrict the TextBox input to numerals, or only a single occurrence of a decimal point, or some other restriction?

  As ever, there are several approaches. If the restriction is something basic, such as numerals only then the easy approach is to use the KeyPress event. What you can do is stop the character from appearing in the TextBox, test to see if it is allowable and, if it is, then allow it to continue.

Letters Only
   To take an example which only allows letters of the alphabet, it would look like this:

    Private Sub TextBox1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox1.KeyPress

        e.Handled = True

 

        If e.KeyChar Like "[A-z]" Then e.Handled = False

 

    End Sub

  In this snippet, it is the e.Handled = True which blocks the input temporarily. The next line assesses whether the key press is a letter of the alphabet*, either lower or upper case, and if it is then the handled setting is reversed. This allows the key press to be passed to the TextBox display. If it fails the test, the block on this key press remains.

  * Depending on your locale and keyboard, some other keys are allowed. These include symbols that are used in combination with characters in some languages, such as accents. In most cases this is the behaviour you will want.

Specific Keys
Sometimes you may want to allow certain keys. A common situation is where you will let the user use the Backspace to correct an error when inputting:

    Private Sub TextBox2_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox2.KeyPress

        e.Handled = True

 

        If e.KeyChar Like "[A-z]" _

        Or e.KeyChar = Chr(&H8) Then

            e.Handled = False

        End If

 

    End Sub

  In this case, it is the Chr(&H8) which identifies and allows the Backspace.

Numbers Only
  Another common requirement is to restrict input to numerals. Of the several possible approaches, using IsNumeric is one of the most straightforward:

    Private Sub TextBox3_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox3.KeyPress

        e.Handled = True

        If IsNumeric(e.KeyChar) Then e.Handled = False

    End Sub

  Sometimes that is too restrictive though. What happens if you want to allow the user to enter decimal points or (depending on their locale) commas to break up large numbers? Allowing these individual characters is simple, but there is another potential catch as we will see in a moment:

    Private Sub TextBox4_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox4.KeyPress

        e.Handled = True

        If IsNumeric(e.KeyChar) _

        Or e.KeyChar = "." _

        Or e.KeyChar = "," Then

            e.Handled = False

        End If

    End Sub

Only One Decimal Point
  In most cases where users are inputting numeric values you will want to restrict them to a single decimal point. The code above will allow multiple entries. Again, there are several solutions, but the following one will usually do the job:

    Private Sub TextBox5_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles TextBox5.KeyPress

        e.Handled = True

        If e.KeyChar <> "." Then

            If IsNumeric(e.KeyChar) Then e.Handled = False '

        ElseIf TextBox5.Text.Contains(".") Then

            MessageBox.Show("Only one decimal point allowed")

        Else

            e.Handled = False

        End If

    End Sub

Command Keys
   If you use any of the previous methods, you will be able to control the standard input keys. But there is another group of keys - Command keys - which won't be excluded by the use of the e.Handled approach. These include such keys as Home, End, Tab, and so on. You may risk alienating your users by excluding these, but there may be times when it is reasonable to do so, in which case you'll need to know how.

  A good way is to intercept the message at the window level and you can do this by overriding the ProcessCmdKey function. Here's how:

  Create a new class which inherits from the basic TextBox. Override the ProcessCmdKey function and test for the currently pressed key in a similar way to that used in the earlier examples. If the key is one you want to suppress then you return True and the Windows message pump will ignore it.

  The following code will be all you need:

Public Class CustomTextBox

 

    Inherits System.Windows.Forms.TextBox

 

    Sub New()

        Me.BackColor = Color.Azure

    End Sub

 

    Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean

 

        ' Declare a variable of type Keys enumeration

        ' named keyPressed.

        ' Cast the msg's WParam as a KeyEnum value

        ' and assign it to the keyPressed variable.

 

        Dim keyPressed As Keys = CType(msg.WParam.ToInt32(), Keys)

 

        ' Process the key that is pressed.

        '  If keyPressed = Keys.Home Or keyPressed = Keys.End Then Return True

        If keyPressed = Keys.Tab Then Return True

 

        ' Return the Command key message

        Return MyBase.ProcessCmdKey(msg, keyData)

 

    End Function

 

End Class

  The light blue back color is simply to make this sub-classed TextBox look slightly different from the default one for demo purposes, but of course is not a key part of the key checking functionality. As you can see, my example blocks the Tab key. You can add or replace other keys, such as Home and End.

Multiple Options
  Handling the KeyPress is fine if you only have a few TextBoxes for which you are controlling input. If there are going to be a lot of them throughout your application, or if you have different input rules for several TextBoxes, then again it may be worth your while to create your own inherited version.

  The following example deals with some of the previous scenarios, but allows the input rule to be selected from an enumeration of choices. The choices used here remain basic, but of course you can expand this idea much further.

  Here is the code:

Public Class RestrictedTextBox

    Inherits System.Windows.Forms.TextBox

 

    Enum RestrictionCategory

 

        NoRestriction

        NumeralsOnly

        LettersOnly

        AlphanumericOnly

 

    End Enum

 

    Private _allowedKeys As RestrictionCategory

 

 

    Property AllowedKeys() As RestrictionCategory

 

        Get

            Return _allowedKeys

        End Get

 

        Set(ByVal Value As RestrictionCategory)

            Select Case Value

                Case 1 To 3 ' One of the enum choices

                    _allowedKeys = Value

                Case Else ' No restriction

                    _allowedKeys = 0

            End Select

 

        End Set

 

    End Property

 

 

    Protected Overrides Sub OnKeyPress(ByVal e As KeyPressEventArgs)

 

        MyBase.OnKeyPress(e)

 

        '  Test whether key is allowed, based on the current choice

        '  from the enum

 

        Select Case _allowedKeys

 

            Case 1 'Numerals only

 

                If IsNumeric(e.KeyChar) Then

                    Exit Sub

                Else

                    e.Handled = True

                End If

 

            Case' Letters Only

 

                If e.KeyChar Like "[A-z]" Then

                    Exit Sub

                Else

                    e.Handled = True

                End If

 

            Case' Alphanumeric

                If e.KeyChar Like "[A-z]" _

                Or IsNumeric(e.KeyChar) Then

                    Exit Sub

                Else

                    e.Handled = True

                End If

 

        End Select

 

    End Sub

 

End Class

  The key areas are the enumeration which is called RestrictionCategory. These are automatically assigned values from 0 to 3. The Property AllowedKeys and its backing Field carry out the standard roles of a Property, the user being able to set the AllowedKeys property in code. (You could improve this by having the property appear in the Properties Window).

  The core of this class is the overridden OnKeyPress method. This checks for the chosen enumeration and then either allows or applies the blocking filter to the currently pressed key. This works in a very similar way to the individual KeyPress approach used in the earlier examples.

  By default, all keys will be allowed and to set the enumeration of your choice, you simply include code similar to the following somewhere appropriate in your form (I've used the Form Load event for my example):

  Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Me.RestrictedTextBox1.AllowedKeys = RestrictedTextBox.RestrictionCategory.NumeralsOnly

 

    End Sub

Summary
  I think that a combination or extension of any of the above approaches will enable you to control exactly what you will allow the user to input into a TextBox.

posted @ 9:28 AM | Feedback (1)