jump to navigation

Masking input to a WPF TextBox February 15, 2007

Posted by Karl Hulme in .Net, C#, WPF, XAML.
20 comments

This blog is no longer actively maintained. The content is well over 5 years old – which is like 50 coder years. Use at your own risk!

How do you go about masking the input to a textbox.  Say you wanted to allow numbers but not letters or any punctuation, how would you do it?  Well the simplest way is to trap the TextChanged event…

        private void TextChangedEventHandler(Object sender, EventArgs e) 
        { 
            TextBox textBox = sender as TextBox; 
            Int32 selectionStart = textBox.SelectionStart; 
            Int32 selectionLength = textBox.SelectionLength; 

            String newText = String.Empty; 
            foreach (Char c in textBox.Text.ToCharArray()) 
            { 
                if(Char.IsDigit(c) || Char.IsControl(c)) newText += c; 
            } 

            textBox.Text = newText; 

            textBox.SelectionStart = selectionStart <= textBox.Text.Length ? 
                selectionStart : textBox.Text.Length; 
        }

This method is simple to understand (always a positive) but there are a few problems with this as I see it, namely..

  • An additional TextChanged event is raised when input is rejected
  • You can’t tell what the change was, you can only inspect the end result
  • You have to re-implement the code that determines the correct caret position

This is not a great way to solve the original problem, because we’re basically hacking at the input after it’s already been accepted.  Leaving us trying to fix the text and compensate for the changes in caret position.  It’s easier to reject invalid characters as they’re presented.  Let’s start by defining a very simple method for stripping out the input we don’t want..  

        private Boolean IsTextAllowed(String text) 
        { 
            foreach (Char c in text.ToCharArray()) 
            { 
                if (Char.IsDigit(c) || Char.IsControl(c)) continue; 
                    else return false; 
            } 
            return true; 
        }

This could be defined slightly more elegantly using a predicate..

        private Boolean IsTextAllowed(String text) 
        { 
            return Array.TrueForAll<Char>(text.ToCharArray(), 
                delegate(Char c) { return Char.IsDigit(c) || Char.IsControl(c); }); 
        }

We then need to identify the entry points to the control and for each one, identify how we will be notified about it’s use.  For typical a TextBox these entry points might be:

  • Typing into the TextBox
    • Hook up to the PreviewTextInput event
  • Copy/Pasting into the TextBox
    • Hook up to the DataObject.Pasting event

It’s easy to implement and hook up the handlers for these events..

      <TextBox PreviewTextInput="PreviewTextInputHandler" 
               DataObject.Pasting="PastingHandler" />

        // Use the PreviewTextInputHandler to respond to key presses 
        private void PreviewTextInputHandler(Object sender, TextCompositionEventArgs e) 
        { 
            e.Handled = !IsTextAllowed(e.Text); 
        } 

        // Use the DataObject.Pasting Handler  
        private void PastingHandler(object sender, DataObjectPastingEventArgs e) 
        { 
            if (e.DataObject.GetDataPresent(typeof(String))) 
            { 
                String text = (String)e.DataObject.GetData(typeof(String)); 
                if (!IsTextAllowed(text)) e.CancelCommand(); 
            } 
            else e.CancelCommand(); 
        }

In some cases, you may need to support other scenarios (such as allowing users to Drag-and-Drop text onto it) and these can be dealt with in a similar fashion – identify an appropriate event, check the input, and if it’s invalid then prevent any further processing.

I hope this information serves as a starting point for implementing a masked textbox.  That is, display an actual mask (using underlines) and rigidly control the input of the user.  One immediate change is that you’d look to subclass TextBox (or base class) and override the protected handlers (e.g. OnPreviewTextInput) rather than hook up directly to the events.  The underlines are also important because without them the user has little clue what is expected of them.  Infact, in a basic validation scenario I think it better to accept dodgy input and then provide UI cues as to why it isn’t acceptable.  I’m holding off doing this myself just in case it turns up in the standard library, perhaps when Orcas ships?  If it doesn’t, I’ll post animplementation because my project needs one. 

XPath Variable/Dynamic Parameters in WPF Binding February 14, 2007

Posted by Karl Hulme in .Net, WPF.
11 comments

This blog is no longer actively maintained. The content is well over 5 years old – which is like 50 coder years. Use at your own risk!

The XML binding support in WPF is excellent and just about anything you’ve ever wanted to know about it’s binding support can be found on Beatriz Costa’s blog.  However, as raised by Grae Foster in a recent WPF Forum posting the Binding support is not without limitations such as the lack of support for parameterising an XPath expression.  Let’s just confirm what it can do..

<Binding XPath="root/reports/report[key='5']" />

If you want to parameterize your XPath expression with literal values, in this case ‘5’, then you can do it with little bother.  This would be useful if you had a button which filtered your xml source based on specific well known values, say enum values, but it’s no good if the value comes from somewhere else in the app.  Unfortunately this second scenario is very common. 

Another, unsupported scenario that causes problems is if you want to allow the user to make a selection from one part of the xml, and use it to select a separate section from the xml.  Sounds a bit specialist?  It’s not, and better explained by example..

 

<root xmlns=""> <categories> <category name="cat1"> <reports> <report key="rep1" /> <report key="rep2" /> </reports> </category> <category name="cat2"> <reports> <report key="rep1" /> </reports> </category> </categories> <reportdefinitions> <reportdef defkey="rep1" name="Report1" Description="This first report has the following prop..." /> <reportdef defkey="rep2" name="Report2" Description="For the second report I decided to..." /> </reportdefinitions> </root>

In the xml above, the categories would be bound to a ComboBox so that the user can select one.  Below this would be a ListBox, which would display the name of the reports in that category.  The finished app is shown below.

The difficulty in getting this (apparently simple) master-detail app to work is that the ‘master’ xml is in a separate part of the document to the ‘detail’ xml.  This post explains how you can resolve this issue.

First wrap the xml data island up in an XmlDataProvider, and key it as xmlData.  Then bind the category elements to a ComboBox.  Finally define a template so that the category name is displayed for the user.  All straight forward..

<ComboBox Name="combo" DataContext="{StaticResource xmlData}" ItemsSource="{Binding XPath=root/categories/category}"> <ComboBox.ItemTemplate> <DataTemplate> <TextBlock Text="{Binding XPath=@name}" /> </DataTemplate> </ComboBox.ItemTemplate> </ComboBox>

Now we need to bind the ListBox.  We know that the contents of the ListBox has to depend on the user selection in the ComboBox, so represent this by binding the DataContext of the ListBox to the SelectedItem of the ComboBox.  The ListBox.DataContext will now be a category element (or null).  Then create bindings on the ListBox ItemTemplate which will pull out the name and description of the reports that correspond to the selected category.  All sounds fine, but there’s no XPath statement defined.

<ListBox DataContext="{Binding ElementName=combo, Path=SelectedItem}"> <ListBox.ItemsSource> <Binding XPath="what goes here??" /> </ListBox.ItemsSource> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding XPath=@name}" /> <TextBlock Text=": " /> <TextBlock Text="{Binding XPath=@Description}" /> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox>

From the perspective of the ListBox, our DataContext will be a category element but we need a set of report elements.  Thankfully the Binding class has a Converter parameter, which can be used to convert the input values of a binding.  Bingo – that’s exactly what we want to do.  I’ve defined an XmlConverter class that can be used to perform that conversion, which would be used as follows..

<ListBox DataContext="{Binding ElementName=combo, Path=SelectedItem}"> <ListBox.ItemsSource> <Binding XPath="reports/report/@key" Converter="{StaticResource xmlConverter}" > <Binding.ConverterParameter> <this:XmlConverterParameter XPathTemplate="/root/reportdefinitions/reportdef[{0}]" XPathCondition="@defkey='{0}'" /> </Binding.ConverterParameter> </Binding> </ListBox.ItemsSource>

The XPath property of the ListBox Binding is used to select the specific @key values that uniquely identify the report elements that we want from the other part of the xml.  So the input to the conversion is now a collection of XmlAttributes.  Ok, but let’s imagine for a second that we had literal values, instead of XmlAttributes.  What would the XPath look like?

/root/reportdefinitions/reportdef[@defkey=’rep1‘ or @defkey=’rep2]

We don’t have literal values, so this needs to be broken down into 3 parts.  An XPath template, an XPath condition and the XPath condition values.  The XPath template (shown in red) is the bit that identifies what should be bound to the control, in our case we want the reportdef elements.  The XPath condition (shown in blue) will be evaluated to determine which of the elements identified by the XPath template will be included, in this case the ones for the chosen category.  In our case above the condition appears twice because there’s two condition values (shown in green) in the current category.  The XPath template and XPath condition are declared in the xaml as shown above.

So that’s all there is to it, in terms of xaml.  Just need to explain what the XmlConverter and XmlConverterParameter classes actually do.  The XmlConverterParameter class is just a DependencyObject with 2 DependencyProperties declared.  (Bear in mind this means that these values can also be data bound.)

public class XmlConverterParameter: DependencyObject { // An XPath statement that contains a {0} where the condition is to be inserted public String XPathTemplate { get { return (String)this.GetValue(XPathTemplateProperty); } set { this.SetValue(XPathTemplateProperty, value); } } public static readonly DependencyProperty XPathTemplateProperty = DependencyProperty.Register( "XPathTemplate", typeof(String), typeof(XmlConverterParameter), new PropertyMetadata(null)); // An XPath condition that contains a {0} where the value from the source XmlNode's will be placed public String XPathCondition { get { return (String)this.GetValue(XPathConditionProperty); } set { this.SetValue(XPathConditionProperty, value); } } public static readonly DependencyProperty XPathConditionProperty = DependencyProperty.Register( "XPathCondition", typeof(String), typeof(XmlConverterParameter), new PropertyMetadata(null)); }

And the XmlConverter?  Well that’s pretty simple too, it just implements IValueConverter.Convert.  This method checks the input, it then builds up the condition using the given XmlNode array (in our case the @key values) and then executes the XPath statement against the xml to find the resultant xml (in our case the reportdef elements).

public class XmlConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // check that we've got all the inputs we need if (value as IEnumerable == null) return null; if (parameter as XmlConverterParameter == null) return null; XmlConverterParameter cp = parameter as XmlConverterParameter; if (cp.XPathTemplate == null || cp.XPathCondition == null) return null; // build the condition part of the xpath statement using the given XmlNode[] XmlDocument doc = null; List<String> compoundCondition = new List<String>(); foreach (XmlNode node in (IEnumerable)value) { if (doc == null) doc = node.OwnerDocument; compoundCondition.Add(String.Format(cp.XPathCondition, node.InnerText)); } // select the node set if (doc == null) { return null; } else { String select = String.Format(cp.XPathTemplate, String.Join(" or ", compoundCondition.ToArray())); return doc.SelectNodes(select); } } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new Exception("The method or operation is not implemented."); } }

The XmlConverter class clearly won’t support every scenario.  If you have another common scenario, you could create a different XmlConverterParameter object which allows the XPath query to be built up in a different way.  But what if you want a complete solution?  Well then you need to address the fundamental problem… you need a powerful expressive way of selecting one set of nodes given another set of nodes.  Three methods spring to mind…

First you could use XSL.  You could implement an XslConverter, to replace the XmlConverter class which accepts an XSL fragment to perform the select.  However XSL uses XPath and so you’re likely to run into the same problems, specifically, finding a way to merge the source multiple input XmlNodes with the ‘select’ statement.  An XSL solution also presents problem of producing a new XmlDocument as the output, which would probably have to be related back to the original xml data.  Second thoughts, this isn’t a solution at all.

Second, you could use XQuery.  This is a much better tool for the job, although it’s been cooking for so long that the world has moved on.  In addition, Microsoft dropped support for XQuery along time ago (Microsoft.Xml.XQuery).  You can still get their implementation of it from 4guysfromrolla, and it seems to work fairly well, but obviously this is a non-supported approach.  Microsoft did include an implementation in Sql Server 2005, so you could (in theory) off-load the processing to it, but there’s far more drawbacks than benefits to this approach.  XQuery support may re-appear but i believe the architect on this project, Michael Brundage, is now working on the XBox – so I wouldn’t hold your breath.

Thirdly, you could use XLinq, and you can get an overview here.  It provides all the expressiveness of XQuery, along with countless other advantages.  This is the best solution, and will kill the parameterized XPath problem dead, by allowing you to perform a join on the xml.  I suspect that’s why the XPath parameterization problem has been overlooked for now.  It simple wasn’t worth producing a half way house solution.  You can download the May 2006 CTP to create your own Binding Converter that uses XLinq to do the work.  If you can wait though, the necessary functionality will probably appear natively, e.g. in the Binding class, or probably something much better that I never would have thought of. 

Tweaking the CodeFormatter plugin for Windows Live Writer February 13, 2007

Posted by Karl Hulme in .Net, Windows Live Writer.
2 comments

This blog is no longer actively maintained. The content is well over 5 years old – which is like 50 coder years. Use at your own risk!

I downloaded the binaries of the CodeFormatter plugin for Windows Live Writer (the blogging tool) from Steve Dunn’s blog.  He’s provided the plugin in binary form so you can just extract the contents to your \Windows Live Writer\Plugins directory.

One small problem with it though, is that the code it produces is enormous.  I’m not talking about code bloat, i mean physically on the screen it’s huge.  To make matters worse it doesn’t scroll, which means you can end up losing half of the code.  Luckily the source is also available. 

Using the source you can easily edit the HTML that’s produced.  For some strange reason I can’t get it to pick up the font-family correct in the end result, but it looks ok in the editor so i think that’s something to do with the template i’m using on word press.

sb.Append(@"<div style=""width: 430px; max-height: 500px; overflow: scroll; font-family: 'Courier New', monospace; font-size: 7pt;""><pre style="""); if ( this.BackgroundColor != Color.Empty ) sb.AppendFormat( @"background-color:{0};", ColorTranslator.ToHtml( this.BackgroundColor) ); if ( this.WordWrap ) sb.Append( @"white-space:-moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; white-space: pre-wrap; word-wrap: break-word;" ); sb.AppendFormat( @""">{0}</pre></div>", s );

If you don’t have time to edit the files yourself then you can grab the binaries that I rebuilt.  If anyone has a problem with me reposting here then let me know and i’ll take them down.

There’s more plugins on the codeplex site here, which includes a PasteAsHtml plugin by Bryant Likes.  It installed easy enough but I didn’t have much luck with it.

Easy Sci-fi Style Screen Readout February 12, 2007

Posted by Karl Hulme in .Net, WPF.
3 comments

This blog is no longer actively maintained. The content is well over 5 years old – which is like 50 coder years. Use at your own risk!

I’m working on a sci-fi style screen readout control in WPF. Basically you can throw strings at it, and it will print them on the screen one character at a time. When you add the next, the first one slides down the page to make room for it. Nice 😉 I’ve got a lot more work to do on it, but I thought it best to share some of the code before it becomes a 20,000 line opus. Sadly the screen shot doesn’t do it justice – not least because I don’t want to bloat the xaml yet with gradient fill overload.

First I need a class to represent the individual readouts. I’ll be binding to it so it’ll need to subclass DependencyObject and all the properties will need to be of a DependencyProperty type.

public class LineItem: DependencyObject { public String Text { get { return (String)this.GetValue(TextProperty); } set { this.SetValue(TextProperty, value); } } public static readonly DependencyProperty TextProperty = DependencyProperty.Register( "Text", typeof(String), typeof(LineItem), new PropertyMetadata(null)); public LineItem(String text) { Text = text; } }

For most of my apps these days I centralise the data. It makes it much easier to introduce caching and performing optimisations later. I’m clearly over-selling here, all we need is a class that we can hook an ObjectDataProvider up to.

 

public static class Model { private static ObservableCollection<LineItem> lineItems = new ObservableCollection<LineItem>(); public static ObservableCollection<LineItem> GetTickerItems() { return lineItems; } }

Next we need to add an event handler for a button we’re going to hook up later. This is placed into the partial class for the WPF window.

public partial class Window1 : System.Windows.Window { public Window1() { InitializeComponent(); } private void ButtonClick(Object sender, RoutedEventArgs e) { LineItem item = new LineItem("Item " + Model.GetTickerItems().Count.ToString()); Model.GetTickerItems().Insert(0, item); } }

The button click handler will create a new instance of our LineItem class and add it to the model. Since the model encompasses an ObservableCollection class, the WPF controls will be notified of the changes automatically.

Ok, that’s the easy bit. Now comes the other easy bit. The basic idea is that the when a new LineItem is added to the collection a new control will be created for it automatically in the StackPanel using the DataTemplate provided. I will use the FrameworkElement.Loaded event to provide the animation. This animation is achieved by sizing the control height from 0 to 15 pixels, and then sizing it from 0 to 100 pixels in width. This is done using key frames. To spice things up, I added a LinearGradientBrush which is used to fade the child controls out at the bottom of the StackPanel.

 

<Window x:Class="Ticker.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:this="clr-namespace:Ticker" Title="Ticker" Height="300" Width="300"> <Window.Resources> <ObjectDataProvider x:Key="tickerLines" ObjectType="{x:Type this:Model}" MethodName="GetTickerItems" /> <LinearGradientBrush x:Key="VerticalGrayScale" StartPoint="0.5,0" EndPoint="0.5,1"> <GradientStop Color="Black" Offset="0.6" /> <GradientStop Color="Transparent" Offset="1.0" /> </LinearGradientBrush> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="0.2*" /> <RowDefinition /> </Grid.RowDefinitions> <Button Grid.Row="0" Content="Add New Message" Click="ButtonClick" /> <ListBox Grid.Row="1" ItemsSource="{Binding Source={StaticResource tickerLines}}" ScrollViewer.HorizontalScrollBarVisibility="Hidden" ScrollViewer.VerticalScrollBarVisibility="Hidden" OpacityMask="{StaticResource VerticalGrayScale}"> <ListBox.ItemTemplate> <DataTemplate> <TextBlock Name="block" Text="{Binding Path=Text}"> <TextBlock.Triggers> <EventTrigger RoutedEvent="TextBlock.Loaded"> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="block" Storyboard.TargetProperty="(FrameworkElement.Height)" Duration="0:0:0.5" AutoReverse="False"> <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="15" KeyTime="0:0:0.5" /> </DoubleAnimationUsingKeyFrames> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="block" Storyboard.TargetProperty="(FrameworkElement.Width)" Duration="0:0:1.5" AutoReverse="False"> <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0" /> <LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.5" /> <LinearDoubleKeyFrame Value="100" KeyTime="0:0:1.5" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </TextBlock.Triggers> </TextBlock> </DataTemplate> </ListBox.ItemTemplate> </ListBox> </Grid> </Window>

I’ll endeavour to post some screen shots of the pretty version when it’s finished.

Hello, World! February 12, 2007

Posted by Karl Hulme in Waffle.
2 comments

This blog is no longer actively maintained. The content is well over 5 years old – which is like 50 coder years. Use at your own risk!

Apparently you can use Word to post to your blog. Which begs the question, how easy is this to do. Can a monkey do it? Can I do it? Can I do it while wearing a monkey suit? Clearly that would be difficult because monkeys have big chunky sausage fingers and the keys on my laptop here are quite small.

The answer to this question will of course be self evident.