in

vbCity Blogs

New (temp) place for vbCity Blogs

Ged Mead's Blog

June 2009 - Posts

  • How To Play Sounds and Music in WPF

    Introduction 

    This is a re-post of an item that I had on my old blog.   As it seems to continue to get a lot of hits (when the site is up) I thought I would transfer it over here to the new site.

      It is more of a "here's how" approach than a "why it works that way" one.  Also I will be creating everything in code, not markup.  There are several more approaches and advanced techniques available via XAML that I won't be covering here.

    PART 1  :  The SoundPlayer

    1. Hard Coded File Path

      Starting with possibly the least realistic scenario, you can use the SoundPlayer to play a .wav file by pointing directly to a file via a hard coded path. 

     Code Copy

    Imports System.Media

        Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click
            Dim player As New SoundPlayer("C:\Temp\4WAY.WAV")
            player.Play()
        End Sub
     

         Note the Imports statement at the top. 

        This wave file will play asynchronously (I do wish someone would come up with an easier to type word than that!  -  it's nearly as bad as 'concatenation' )   But I digress....

        If it's a particularly large wav file, you can help things out by inserting a Load statement after the initialization and before the Play fires.  This will tee the player up to play without any kind of pause while it cranks up the file.   This is something well worth bearing in mind as we users have become more and more impatient of delays.

    2.  Use a Content File

       One particularly useful approach which gets around all the potential problems of hard coding file paths is to add the file to your Solution Explorer items.  You can do this with the standard "Add Existing Item" selection from the IDE menu, adding the wav file directly to the list of files for the Project. 

      This now brings me to Frustration #1.  First, here's valid code to play the sound using a SoundPlayer object:

    Code Copy
        Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button6.Click

            Dim player As New SoundPlayer
            player = New SoundPlayer("SND0103.WAV")
            player.Play()

        End Sub

       Now, before we move on, let's check the Solution Explorer:

         

        As you can see the file name is correctly entered and that file is sitting in the Solution Explorer's list of files that (you would think) it knows about.

       The key to fixing this problem is the 'Copy to Output Directory' Property.  By default, this is set to 'Do Not Copy'. 

     

     You need to change this to 'Copy Always' and you'll be back on track.

    3. Audio File as Resource (Frustration #2)

       I wasn't going to include this option because it has generally caused me more trouble than it's worth (especially bearing in mind that there are several relatively trouble-free alternatives).  However, you can add a wav file as a Project Resource as normal and then play that Resource via a Stream.

       The syntax is:

    Code Copy
      Private Sub btnResource_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles btnResource.Click
            Dim player As New SoundPlayer(My.Resources.canon)
            player.Play()

      End Sub

     

       Even with small, well tested files I often found that using this approach I would get a lot of unacceptable sound distortion (actually additional unrelated sounds to that expected).   I have tried it on several PCs,  each with different sound card configurations and the problems occur on them all.

    5.  Other SoundPlayer Features

       In addition to the standard Play method, the SoundPlayer has a PlaySync method - in case you should rather unusually want the system to be locked until the sound has finished playing. 

      And finally if you want a sound to be repeatedly played, you can use the PlayLooping method to drive people mad; sanity can be restored by inserting the Stop method!

    PART 2:  MediaPlayer and Media Element

        As you've already seen, the  SoundPlayer has some limitations.  It only understands wav format files and you can generally only play one wav file at a time (unless you start to get quite fancy with multithreading).  In many cases, the MediaPlayer or MediaElement will be a better option, but there is a good reason why I included the discussion of the SoundPlayer above.  In some situations on particular systems you will have  problems getting the  two Media components to work properly.  So it's always useful to know how to use the lighter weight SoundPlayer if you run into these problems in the future.

    6.  But I Want to Play MP3 Files!

      -  or WMA or MID, etc. for that matter.  And this is a job for either the MediaPlayer or MediaElement.  Keeping to my approach of doing all the work in code, I'm therefore going to home in on the MediaPlayer.  (If you have a need to create a media object in XAML and have it play, pause, stop, trigger, etc, only in XAML then the MediaElement will be your tool of choice.)  

       Before abandoning the MediaElement approach altogether, I should point out that  you can create the MediaElement in XAML and then access it's properties and methods in code.   However, there is no advantage that I know of and in fact, the syntax is slightly more verbose and it also caused Frustration #4 - the fact that the audio file will only play once and then needs to be 'reset' by calling its Stop method; the Stop method apparently has the effect of returning the playhead to the beginning of the file.

      So let's look at the MediaPlayer in WPF.  It has an Open method which loads the file in the form of a URI.  This was Frustration #5 for me, as it took me a while to get past the problem of the "Value of Type 'String' cannot be converted to 'System.URI.'" error.  It took a lot more  than a quick search to finally discover that I needed simply to add the URIKind argument to the Open method; so the considerable amount of time I spent trying to do casts was wasted.   But maybe that's something I should have known. 

      It also has Play, Pause and Stop methods, plus the more sophisticated Balance and Volume properties.

      There is another small Gotcha involved in using this control.  Take a look at the following code snippet:

    Code Copy


    Dim mplayer As New MediaPlayer
    mplayer.Open(New Uri("Godfather.mp3", UriKind.Relative))
    mplayer.Play()

         It's hard to see anything wrong with that code snippet, but if you do try it you'll find that the audio will play for a short period of time and then - for no apparent reason - stop.   This is Frustration #6.

       As I understand it, what happens here is that the new MediaPlayer object is created, passed a URI for its source and then instructed to play.  Play begins but at that point seemingly the MediaPlayer closes itself, saying  "I've set the audio away, so my work here is done".  The end result is that at some unpredictable point in time the closed MediaPlayer is released from memory and will be disposed.  And of course as soon as it no longer exists there is no means for the sound to continue to be played.  So it stops in mid note!

       Anyway, having dragged you  laboriously through that explanation, you'll probably already have realised that the fix is simple.  All you need do is instantiate the MediaPlayer outside the scope of the click event so that the MediaPlayer will continue to exist throughout the lifetime of that Window or Page.  In other words, like this:

    Code Copy
    Class Window1
        Dim mplayer As MediaPlayer

      Private Sub Button1_Click(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click

            mplayer = New MediaPlayer
            mplayer.Open(New Uri("Godfather.mp3", UriKind.Relative))
            mplayer.Play()

        End Sub

    End
    Class

       As with the SoundPlayer sample, the mp3 file has been added to the project files in Solution Explorer,  its Build property set to Content and its Copy To Output Director set to Copy Always.

      If the file is extra large and you get a pause before it actually begins to play then you can of course move the mediaplayer instantiation and Open code to, for instance, the Window Loaded event or some other place where the delay won't be so obvious.  It will then be teed up ready to go as soon as the Play method is invoked.

    7.  Summary

       There isn't much else to add.  I think you now have the tools you need to  play sound and music files in WPF.  By the way, that does also mean both sounds and music at the same time, because you can have multiple instances of MediaPlayers running concurrently (or MediaPlayer(s) and a SoundPlayer running together).   Therefore  you can have almost any combination of sound files running, subject to the limitations of your system, that is.    

       I hope you will find this information useful in your future projects if you need to include sound and want to avoid the frustration of the learning curve.

      

       

    Posted Jun 29 2009, 03:46 PM by XTab with no comments
    Filed under: ,
  • How To Change WPF ListBox SelectedItem Color

     

     

      In this article, we will look at how to alter the background color that appears when a user selects an item in a WPF ListBox .

      I've been using DataTemplates to organize the layout of WPF ListBoxes for quite a while now. Not only in my WPF Applications, but also in Windows Forms sometimes by using WPF/WinForms Interop (See my article on devcity for info on how to do this).

      If you have used DataTemplates, you will know that they can make a great job of packaging up the data display and allowing you to easily create UIs that would be extremely difficult in Windows Forms.

      I was working on a ListBox creation task this week. Nothing very fancy - each item comprised of an Image and two items of text inside a Gradient Border:

      

      The DataTemplate makes a fair job of tweaking the presentation of the data, but when I ran it and selected an item I still got the standard solid blue selection color:

      

      I thought it would be better if I could replace the standard solid blue with something else, but I found that this wasn't something I could achieve by tweaking the DataTemplate. What is needed is a change to the ItemContainerStyle .

       It's important to understand the role of these two styling tools. The DataTemplate allows you to package up the individual items of data for display. In the case of a ListBox, you can think of the DataTemplate as sitting inside the ListBoxItem. The ItemContainerStyle is one level up from this. It is responsible for the overall look of the ListBoxItem. So if it helps you to picture the respective roles, you could think of the DataTemplate being contained in the ItemContainerStyle, although that's not exactly how it works.

       The question is, how do you change the ItemContainerStyle to create a different selection color and, once this is done, how do you then get the ListBox to apply it?

       The answer to the second question is shorter, so I'll deal with that one first. ItemContainerStyle is a property of the ListBox.

     <ListBox ItemContainerStyle="{StaticResource SimpleListBoxItem}">


    As you would expect, this property takes a Style as its value. And that leads us to the answer to the first question - you create a Style, but (and here's the important bit) you ensure that the Style also contains the ControlTemplate for the ListBox.

       When you stop and think about it, this has to be right. As soon as you find you want to change the fundamental look of the ListBox element, you know you have to work with its Template. How much trouble you then go to in order to rewrite the ControlTemplate is a matter of choice.

       I began by looking at the full ListBox template, which is fairly lengthy, but then realized two things:

    1. I already had my DataTemplate in place to do the fancy stuff, so all I needed to do was find the part of the Template that deals with the change of color when an item is selected and tweak this. Everything else could be left unchanged.
    2. I didn't need to make any changes to the ListBox template. What I was dealing with here was the ListBoxItem template.

       When the requirement is as simple as this, a useful approach is to use the Simple Styles, which are available from many sources. If you haven't come across these yet, they are a cut-down version of the full Styles that are used to create the standard WPF elements. One easy way to access and download them is to enter "Styling with ControlTemplates Sample" in the "Look For" text box of the Index, (not the online Search box) on the Visual Studio Help page. This will reveal the sample page, which includes a link to download all the simple styles.

      So I took the simple style version of the ListBoxItem and changed the Setter for the IsSelected Trigger. Here is the finished Style:

         <Style x:Key="SimpleListBoxItem" TargetType="ListBoxItem">

         <Setter Property="FocusVisualStyle" Value="{x:Null}" />

          <Setter Property="Template">

            <Setter.Value>

              <ControlTemplate TargetType="ListBoxItem">

                <Border

               Name="Border"

               Padding="2"

               SnapsToDevicePixels="true">

                  <ContentPresenter />

                </Border>

                <ControlTemplate.Triggers>

                  <Trigger Property="IsSelected" Value="true">

                    <Setter TargetName="Border" Property="Background"

                         Value="{StaticResource AuthorGradient}"/>

                  </Trigger>

                </ControlTemplate.Triggers>

              </ControlTemplate>

            </Setter.Value>

          </Setter>

        </Style>

       Compared to the full version, that's fairly brief. You will see that I changed the Background color of the Border that contains the ListBoxItem in the Template* from the default to a gradient brush named AuthorGradient that I created and stored in the App.xaml file as an Application scope Resource.

      * Don't confuse the Border in the Style's Child ControlTemplate with the Border that is used in the DataTemplate.

        Now when an item is selected, the user sees this gradient instead of the plain blue:

      

      So the full markup for the ListBox is as follows:

           <ListBox x:Name="TemplatedListBox"

          HorizontalAlignment="Left"  Margin="10"   

          ItemTemplate="{StaticResource GreenDataTemplate}"

          ItemsSource="{Binding}"

          ItemContainerStyle="{StaticResource SimpleListBoxItem}" >

          </ListBox>

       You can see that the ListBox's ItemTemplate is set to the DataTemplate which controls the layout of the data in each ListBoxItem. The ItemContainerStyle contains the template for the ListBoxItem - the one where I changed the selected item color from plain blue to gradient green.

      In order for this particular example to work, you will of course need to have a DataSource available to the ListBox's ItemsSource, to which it can bind. However, you don't need to have a bound ListBox, as you will see at the end of this item.

      The DataTemplate I used to create the Border/Image/TextBlocks combination is:

         <DataTemplate x:Key="GreenDataTemplate" >

          <Border BorderBrush="{StaticResource GreenGradient}" BorderThickness="4"

                 CornerRadius="4" Margin="4,2">

            <StackPanel Orientation="Horizontal">

              <Image Source="{Binding Path=ImagePath}" Margin="2,0,4,0"

                Width="60" Height="70" Stretch="UniformToFill"   />

     

              <StackPanel Margin="1" Width="120">

                <TextBlock Padding="2,17,5,2"

                   FontSize="13"

                    FontWeight="Bold" Foreground="DarkGreen"

                    Text="{Binding Path=Name}" Height="56">

                </TextBlock>

     

                <TextBlock Foreground="Green"

                   Margin="12,2,0,2"

                   Text="{Binding Path=Published}" >

                </TextBlock>

     

              </StackPanel>

            </StackPanel>

          </Border>

        </DataTemplate>

       I placed this in the Window.Resources block. Again, this will only work if you have a DataSource which contains the three fields I have used in the Bindings for the Image and TextBlocks - ImagePath, Name and Published.

      If all this DataBinding and DataTemplates is confusing or more than you need, and you simply want to change the selected item background color, then the following ListBox will be sufficient:

        <ListBox ItemContainerStyle="{StaticResource SimpleListBoxItem}">

          <ListBoxItem> <TextBlock Margin="4" Padding="5" FontSize="14">Item 1</TextBlock></ListBoxItem>

          <ListBoxItem>

            <TextBlock Margin="4" Padding="5" FontSize="14">Item 2</TextBlock>

          </ListBoxItem>

        </ListBox>

       When an item is selected, the ItemContainerStyle will kick in and the user will see the gradient:

      

      And finally, if you don't even want the gradient, but just need a different solid color, then simply make the change in the Style:

         <Style x:Key="SimpleListBoxItem" TargetType="ListBoxItem">

         <Setter Property="FocusVisualStyle" Value="{x:Null}" />

          <Setter Property="Template">

            <Setter.Value>

              <ControlTemplate TargetType="ListBoxItem">

                <Border

               Name="Border"

               Padding="2"

               SnapsToDevicePixels="true">

                  <ContentPresenter />

                </Border>

                <ControlTemplate.Triggers>

                  <Trigger Property="IsSelected" Value="true">

                    <Setter TargetName="Border" Property="Background"

                         Value="Yellow"/>

                  </Trigger>

                </ControlTemplate.Triggers>

              </ControlTemplate>

            </Setter.Value>

          </Setter>

        </Style>

       As you can see, in this case, I have changed the highlight color to Yellow by editing the Setter for the Border in the IsSelected trigger. The result will be:

      

    Incidentally, although it's not an issue with this particular ListBox, you may sometimes find that the first item in a ListBox is selected and highlighted by default.  Inserting the usual 'SelectedIndex=-1' in the Window's Loaded event doesn't always work, but you have a useful alternative in WPF - the ContentRendered event.  You can use the SelectedIndex approach or UnselectAll, as shown in the snippet below: 

        Private Sub TemplatedList_ContentRendered(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.ContentRendered

            TemplatedListBox.SelectedIndex = -1

     

            ' Alternative

            TemplatedListBox.UnselectAll()

        End Sub

    So, whether you want to create multicolor effects, tied in with bound DataSources, or even if you simply want to change the selected item solid color, you now know how to do it.

  • Comparing WPF in Visual Studio 2008 with Silverlight 2

    This will be something of a 'Work in Progress' blog item, as I plan to jot down differences between WPF and Silverlight as I find them. It can be difficult to keep up to speed on these. The SDKs, Toolkits, roll out of new editions of both platforms will all cause the sands to keep on shifting. Anyhow, I hope those that I do identify will be of help to those of you who are familiar with WPF, but maybe not so much with Silverlight.

    The topic headings are listed alphabetically.

    Button Content
    In WPF, you can create a Button and assign its Content in the following way:

     <Button Margin="5" >Silverlight</Button>



    In Silverlight, you don't have this facility and have to explicitly identify the Content property:

     <Button Margin="5" Content="Silverlight"></Button>

     

    Button ClickMode
    This is more a matter of user expectation than different properties. The WPF Button has a ClickMode property. This is available in both WPF and Silverlight. The three available values are:

    • Hover
    • Press
    • Release
    As you would expect, when the Click event of the button will fire depends on this particular setting.
    The reason why I include it as a difference is that I think it is unlikely that you will often use the 'Hover' option in a desktop application, because that isn't a reaction your users will expect. In a Silverlight, browser based environment, of course, it may well be that users will be happy with a button click firing when they hover over a button. (Actually, I'm not sure that 'hover' is totally accurate. It really seems to fire on the immediate mouse enter action and I haven't found a setting that allows a built in delay.)

     

    The default value - both for WPF and Silverlight - is 'Release'.

    Chrome and Decorators
    Most of the Chromes and Decorators, which can be used to fine tune control templates to a very detailed level, are not available in Silverlight2.

    Commands
    Silverlight 2 does not support the WPF Commands structure. It does however support the ICommand interface.

    Controls (Visual Elements)
    It's hard to keep up with the permutations and changes, but I think the following breakdown is accurate:

    1. Available in WPF (.NET 3.5, Visual Studio SP1), but not in Silverlight 2:

    • FlowDocument Readers
    • Frame
    • Glyph
    • GroupBox
    • InkCanvas
    • ListView
    • Menu
    • NavigationWindow *
    • Page *
    • PageFunction *
    • RichTextBox
    • StatusBar
    • UniformGrid
    • VirtualizingStackPanel
    • WebBrowser
    • Window
    • WindowsFormsHost
    * These items are not necessary in Silverlight, because it is browser based.

    2. Available in Silverlight 2 , but not in WPF with Visual Studio 2008:
    • DataGrid *
    • DatePicker *
    • HyperlinkButton
    • MultiScaleImage

    * These are not included in WPF in Visual Studio 2008, but are downloadable as part of the WPF Toolkit and will almost certainly ship with VS 2010. You can see info and download the WPF Toolkit from here.

     


    3. Available in the Silverlight 2 Toolkit and in WPF in Visual Studio 2008.
    • Calendar
    • Expander
    • GridSplitter
    • TabControl
    • TreeView
    • ViewBox
    • WrapPanel

    4. Available in the Silverlight 2 Toolkit, but not in WPF.
    • Accordion
    • AutoCompleteBox
    • ButtonSpinner
    • Data Visualization/Chart Tools
    • DomainUpDown
    • NumericUpDown
    • TimePicker
    • TransitioningContentControl


    5. Available in the WPF SDK, but not in Silverlight 2.
    • Ribbon

     

    DataBinding

    • Silverlight 2 does not support One-Way To Source binding.
    • With WPF, the default binding mode is dependent upon the dependency property, but is usually TwoWay. The default binding mode in Silverlight 2 is OneWay.
    • Silverlight does not support Explicit DataBinding. It does however support the key approaches of LostFocus and PropertyChanged.
    • Silverlight 2 does not support MultiBinding.
    • Silverlight 2 does not support Element Binding (e.g. Binding a Slider value directly to a property on another element).
    • Silverlight 2 does not support the ObjectDataProvider.

     

    DependencyProperties
    Silverlight 2 does not support Read Only DependencyProperties.

    Deployment Options
    A Silverlight application is deployed to a web server and hosted in a web browser. (The next version of Silverlight should also include the ability to view the application offline). WPF applications may be deployed as a standard installed application or via Click Once. WPF also offers a browser hosted version - the XAML Browser Application (XBAP). In view of the limitations of XBAPs it is likely that most developers would now choose Silverlight over WPF for browser hosted applications.

    DrawingBrush
    The WPF DrawingBrush is not supported in Silverlight 2. However, there is little that this brush does that can't be achieved with other approaches. The key loss would be the ability to tile, but there are workarounds for this.

    Effects
    Effects (as opposed to BitmapEffects, which are no longer recommended for use) are only available in WPF. The next version of Silverlight should have the ability to create Pixel Shaders, and therefore Effects.

    File Access
    WPF has full access to local files in the same way as other .NET desktop applications. Silverlight runs in a Low Trust environment, so Users have restricted access to local files. Access to files in Silverlight is only available by means of the OpenFileDialog and even then is effectively only Read Only access. In Silverlight 2, saving data to file can only be achieved by using Isolated Storage.

    Hardware Acceleration
    By default, WPF hands off much of the heavy duty graphics work to the GPU, assuming that the graphics card meets the minimum specification. Silverlight uses software rendering.

    Hyperlinks
    In WPF you can create a Hyperlink by using the Hyperlink element. Generally you use these in Page based applications and FlowDocuments. Silverlight uses the HyperlinkButton.
    Both elements use the NavigateUri property to assign the address of the link. The Silverlight version seems to be more versatile. Although it is called 'button', it doesn't have the standard rectangular button appearance. In fact, if you drag one from the Toolbox, you will see its markup in the XAML pane, but nothing shows in the Design pane.
    The intention is that you use the HyperlinkButton as a canvas (with a small 'c', not a Canvas element). You then add Content to the button and this is what creates the interface that the user sees. The Content can be anything from a simple single element to a more complex interface - such as a StackPanel which contains multiple elements:

    <HyperlinkButton NavigateUri="http://www.devcity.net" Width="60"

                    Margin="15" HorizontalAlignment="Left"

                    VerticalAlignment="Top">

          <HyperlinkButton.Content>

            <StackPanel>

              <Image Source="ShelfUnit_WithCans.png"></Image>

              <TextBlock>  Click Me! </TextBlock>

            </StackPanel>

          </HyperlinkButton.Content>

        </HyperlinkButton>

          

     

    Isolated Storage
    There are significant differences between the implementation of local isolated storage in Silverlight 2 and WPF. Silverlight 2 restricts the availability of disk space. The default setting is 1 MB. This may be increased with explicit user authorization.

    Key Enumeration
    WPF contains more precise Keyboard Key definitions in its Key enumeration. For example, it

    KeyEventArgs
    WPF implements several properties in KeyEventArgs; Silverlight implements two - Key and PlatformKeyCode.
    WPF does not implement PlatformKeyCode. This is used in Silverlight to identify non-Windows keys.

    Markup Extensions
    Silverlight only supports a small sub-set of markup extensions. They are:

    • Binding
    • StaticResource
    • TemplateBinding
    • x:Null
    It is not possible to create your own markup extensions in Silverlight.

     

    Mouse Events
    Silverlight supports a limited set of Mouse events:

    • LostMouseCapture
    • MouseEnter
    • MouseLeave
    • MouseLeftButtonDown
    • MouseLeftButtonUp
    • MouseMove
    This means that Silverlight doesn't support mouse wheel, left/right mouse button differentiation or any of the PreviewXX mouse events that are available in WPF.

     

    Network
    Silverlight 2 is limited to asynchronous network calls.
    Silverlight 2 can communicate to Network Resources via Sockets.
    Silverlight 2 supports only SOAP 1.1 over HTTP, and does not support other web services not compliant with WS-I Basic Profile 1.0.

    Path Based Animations
    This feature is not available in Silverlight 2. Linear interpolated and KeyFrame animations are available.

    Resources
    Silverlight 2 does not support MergedDictionaries.
    Silverlight 2 does not include the FindResource method.

    RoutedEvents
    Silverlight 2 only supports Bubbling and Direct Routed Events. WPF also supports Tunneling Events.

    Security
    WPF desktop applications require FullTrust permission in order to run. XBAPs run with partial trust. Silverlight 2 applications run in low trust.

    Styles
    WPF offers the choice of Named Styles and Typed Styles. Named Styles allow you to set the Style on an element by using its Key. Typed Styles do not have a Key and all elements of the TargetType within scope of the Style will be assigned the Style. Silverlight doesn't recognize Typed Styles and if they are used, the assignment of the Style will fail silently. When setting the TargetType, Silverlight does not use the markup extension. You simply assign the name of the TargetType in double quotes. If you forget, and force of habit makes you add the curly braces and markup extension in Silverlight, you won't get a design time error, but it will fail at run time.

    Tiled Brushes
    Silverlight 2 doesn't support the TileMode in ImageBrush.

    Transitions
    Silverlight will not accept Triggers and uses the Visual State Manager (VSM) to organize animations and transitions for user interaction. VSM's capabilities currently are limited to the equivalent of standard Style Triggers and EventTriggers. There are no features equivalent to DataTriggers or MultiTriggers in Silverlight.
    VSM for WPF will be available soon.

    Validation
    WPF supports the creation of ValidationRules. This feature is not available in Silverlight 2.

    Visual Brush
    Silverlight2 doesn't support WPF's Visual Brush. As this is a brush of very limited use, it's not likely to be much of a problem.

    Visual State Manager (VSM)
    Currently this is a Silverlight only feature. It will be introduced into WPF in the future.

    Visual Studio Designer
    Visual Studio 2008 has the facility for you to drag and drop, reposition, resize, etc, elements in the Design pane with WPF. For Silverlight it seems to offer editing tools only in the XAML pane. The Design pane is a kind of 'Read Only' version of what is created in the markup.

    VisualTreeHelper
    This class exists in both WPF and Silverlight 2, but because of differences in the underlying base classes of the two platforms they have different implementations. The key difference is that WPF offers the HitTest method, whereas Silverlight2 uses FindElementsInHostCoordinates.
    With the exception of GetChild, GetParent and GetChildrenCount, Silverlight2 doesn't support any of the GetXXX methods found in WPF.

    Watermarked TextBox
    This is a Silverlight only control. The name says it all - basically a TextBox that allows you to include light shaded text to help the user. If memory serves, this was in Kevin's famous Bag o'Tricks.

    Note: Most of the differences described above are also included in an excellent White paper comparison document published by Wintellect. You can download a copy from here.

    Posted Jun 18 2009, 10:55 AM by XTab with no comments
    Filed under: ,
  • First Steps in Silverlight 2 and Expression Blend 2

     

     As a Developer, Visual Studio is my comfort zone but I thought the time had come to see what the experience of creating a Silverlight 2 project in Expression Blend would be like. Having spent the past six months working almost exclusively on Windows Presentation Foundation, I was particularly interested to find out how different the WPF and Silverlight experience would be.

    The first thing I had to do was update my copy of Expression Blend 2. Out of the box, your only Silverlight option is a Silverlight 1 site. But if you go to the download site here you can download the latest Service Pack. This incorporates the changes that were available in the Blend 2.5 Beta, the main one being that you can build a Silverlight 2 Application.

     Probably the biggest difference between the two versions of Silverlight is that Silverlight 2 gives you the option of using C# or Visual Basic as the code-behind. If, like me, you're not a Javascript expert, but have served your time on the .NET learning curve, this is a really welcome option.

     If you have used WPF, you will know that by default when you create a new WPF Application you are presented with a Window instance containing a Grid as the root element on which to start creating your UI. In Silverlight 2, the default is a user control, again containing a Grid as the root element. As Silverlight is a browser plug in*, rather than the desktop application of WPF, this approach makes sense. However, I have to doubt the wisdom of this User Control being named "Page.xaml" by default, as this might cause confusion to those of us who have also created Pages in WPF Applications and think of Pages as being, well, er, pages!.

     To add some elements to the User Control, you simply drag them from the Asset Library on to the design surface. I added a TextBlock, an Image and a Button.

       

     If you are not familiar with Expression Blend, the Asset Library - where you drag your elements from - is the last icon on the left, which I have circled in Red. I added the png file which is used as the Source property of the Image to the project files.

     A Button that does nothing when clicked won't impress anyone, so my next task was to fix this. My inclination was to switch straight away to Visual Studio, but I thought I would see what Blend had to offer in the way of event handler creation.

     The Properties Pane in Blend has a button which causes the Properties to be listed (the default choice) and next to that there is a button for events. You can see this in the screenshot below.

        

     Expression Blend is essentially a tool for creating visual user interfaces. So, unsurprisingly, all you get when you choose that Events pane button is a list of common events for the particular element or control. What is quite impressive though is that if you type in the proposed name for the event handler (as I have done in the example below) and then hit the Enter key, not only does this cause Visual Studio to fire up, but when you look at the code-behind page you will see that a handler of the name you input will be created and it will have the correct signature. You can then simply insert whatever code for what you want to happen as a result of the button click.

        

     One thing I was keen to try out was Silverlight's Visual State Manager, about which I had heard much, but understood nothing. As it seems certain that WPF will have VSM soon, I wondered how much easier (or not) VSM would be compared to the current WPF Storyboard and Triggers approach.

    I experimented by adding a simple animation to a button, transforming its size when the mouse entered its area. (I will be writing a full article on Visual State Manager soon, which will include VSM for controls which have CommonStates built in for you in Blend - such as the Button - as well as VSM for those that don't, such as Images.)

     The steps for the Button are as follows:

    1. Drag a Button from the Asset Library on to the drawing surface (Note to self: Must get used to calling this the 'Artboard').
    2. Right click on the button and choose 'Edit Control Parts (Template)'.
    3. Select 'Edit a Copy'.
    4. Rename the Key if you want to.
    5. Click OK.

    If you now look at the States Pane :-



    you will see that a standard set of States for a Button object are displayed for you. These are the four main CommonStates

    • Normal
    • MouseOver
    • Pressed
    • Disabled

    and two FocusStates:

    • Focused
    • Unfocused

     

     If you want the visual state to change when the Button is pressed, you add a Transition to the Pressed State in that pane. This is done by clicking on the small 'Arrow and plus symbol' icon to the right of the word 'Pressed' in the States Pane. When you left click on this icon you will see a context menu which shows combinations of state changes. Select the first one - '*>Pressed'. The area round the Artboard should now have a red border and the "State recording is on" message appears at the top left corner. (If for any reason it is not on, simply click the mouse over the dot to the left of the "State recording is off" message.)

      As my initial experiment, I wanted to reduce the width and height of the Button by 15% when it is pressed. Here are the steps:

    1. Select the part of the Button that you want to animate.
    2. Go to the Properties pane.
    3. Scroll down until you reach the 'Transform' section.
    4. Select the Scale transform icon (the arrow coming out of a rectangle).
    5. Change the value of X to 0.85.
    6. Change the value of Y to 0.85.
    7. Press F5 to test the project.
    8. Click on the button to ensure that the animation works.
    9. Close the Browser.
    10. In the Objects and Timeline pane in Blend, click on the 'Return Scope to [UserControl]' icon at the right hand side of the Style name.

     

      I had quite a lot of trouble with this. I found that if I clicked on the button on the Artboard and made the transformations (or any other animations, for that matter), I was not getting the results I expected. I eventually worked out that the trick is to select the exact element you want to animate from the Objects and Timeline pane. In the case of the button transformation, the item to select is the parent grid. In some cases, you may need to expand the dropdown list to get to the exact item you need and you may have to experiment some before you get it right. I think this kind of problem is only likely to occur where you stick with the standard template; clearly, if you build up your own set of elements on the Artboard to make a styled button of your own, you will be more familiar with exactly which part(s) you want to animate.

     Another little thing to watch out for which might confuse at first, is that the States pane seems to 'remember' the last set of States you dealt with. If you then select a different object, (for example, the TextBlock in my initial user Control) and the States haven't been changed or set for this object, you will still see the States from the preceding object in the pane.

     Because I was familiar with the Triggers and Storyboard approach, I did want to see if this was still available as an option in Blend. It doesn't seem to be, but of course I can always do this in XAML in Visual Studio, using an EventTrigger. However, I can see that the Visual State Manager is going to be an easy beast to tame, so I don't think I'll need to revert to the old way very often.

     So at this stage, my first impression is that I will be content to use Blend for complex graphics and animations, gradients and visual states. For most other things, I think I will still be using Visual Studio whenever possible.



     * At least it is a browser plug in for the time being. Silverlight 3, which is coming up fast, is going to offer an out-of-browser experience. That is, you will be able to include code that will let the user access the Silverlight User Control when offline. My first thought was "Isn't that what WPF does?", but then realized that Silverlight is cross-browser, cross-platform whereas WPF is not. It will be interesting to see where this leads in the long term.

  • How To Restrict TextBox Input

     

     

      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.

Copyright 1998-2009 vbCity.com LLC
Powered by Community Server (Non-Commercial Edition), by Telligent Systems