WPF Font Rendering Side-by-side

Thursday, March 04, 2010 / Posted by Luke Puplett / comments (0)

Text rendering in Windows Presentation Framework has been a moot point since its release. Microsoft are responding to this and in WPF 4.0 text rendering will be performed by DirectWrite, some new bit of DirectX. But still, while working on reproducing a mockup done in Corel PHOTO-PAINT, I wanted to get to the bottom of why I am never happy with WPFs rendition and whether I am just imagining the whole thing.

My original mock-up was done in Corel's Photoshop equivalent at a very high resolution and then I view it in a normal viewer like Windows Live Photo Gallery and zoom it down to the right size.

I then compare my WPF work with the mockup PNG side-by-side and usually squint and shake my head alot.

So I created the same thing in CorelDRAW and Microsoft Word, giving me a total of 4 versions of the same mockup. Here's what I discovered.

Font Render Side-by-side

For me, Corel's versions are the nicest. I just like them more, in the way that I prefer one woman's face over another. It's subtle and innate.

Philosophy aside, the WPF version and the Corel could easily be mistaken for different typefaces. They're both Swiss 721 Lt BT. Here's my analysis:

  • Corel renders the O under the T, probably because it doesn't need to consider text selection. This is it primary advantage.
  • Corel apps and Word seem to render the lowercase glyphs slightly larger (1px) so that there is less of a difference in height to the uppercase T.
  • The top right end of the s in WPF sinks down, caused by confused anti-aliasing.
  • Word uses coloured anti-aliasing, blue on one side of the T and red on the other - the others do not.
  • WPF renders the thickest, most woolly lines.
  • WPF also renders the horizontal bar in e lower than the others, probably because it is slightly smaller and the bar snapped down a pixel.
Font Render Side-by-side Zoomed

As far as I see it, the fact that Microsoft Word can render the text quite accurately, the issue is not solely about ClearType but something inherent in WPF. It's a shame that text cannot be tuned per XAML element with varying amounts of anti-aliasing instead of on/off.

I hope they work it out in 4.0.

Labels: , , ,

Luke's Ridiculously Simple MVVM Table Grid

Friday, February 26, 2010 / Posted by Luke Puplett / comments (0)

Windows Phone 7 Series extends the panning and scrolling UI metaphors seen in Windows Media Center and apparently in the Zune. This is no surprise as they are consumer entertainment products, or at least two are and the phone has been remarketed.

Anyway, to experiment with this style of interface, which lends itself so well to touch, I realised that I need a kind of table that can slide left and right, scroll up and down, be extended dynamically and do all this from a control-agnostic view model. Welcome to my ridiculously simple custom control.

Reductionism

When thinking about designing a user interface one must adopt a reductionist mentality. Each consituent part of a UI needs breaking down into units of functionality and within each one, there are usually more units of functionality until a reusable control is arrived at. And then there's that control's constituents which are usually a small number of stock WPF controls.

This idea comes quite easily to an object oriented programmer because its pretty much the same thought process as for designing classes of logic.

To be useable from a ViewModel that is not aware of the control, perhaps because the assembly that the view model is in doesn't reference the library of custom controls, it must support data binding using CLR base types.

A table is a bunch of bunches of stuff and at the time of writing, there's only one bunch of stuff class that supports databinding and that's the ObservableCollection<T>.

The Phone 7 interface scrolls sideways between 'pages' of contextual content either by gesture or with a click of a 'column' header - itself in its own slider.

500x_xboxlivephone

Each page scrolls as a normal page, with autohide scrollbars like the iPhone. The simplest logical representation of this logic - remember controls are just logic - is a horizontal StackPanel of ListBox controls.

That harmoniously translates into CLR land as an ObservableCollection of ObservableCollection objects. Nice.

To allow of column headings, I'm going to wedge a KeyValuePair in there, where K is a string and V is the ObservableCollection<object>

Code

Here's the full code the control. I'll explain some of the few remarkable points after. However there is a bug bug in this quick attempt: DataBinding doesn't call the setter and so doesn't set the CollectionChanged handler, which is why it works in my non-bound example.

The solution is to add/remove the handlers for this event in the OnTableSourceChanged handler, but I'm sure most of you can work this out. Is that lazy? Sorry but editting formatted code in the published article is a nightmare.



namespace WpfSimpleTableControl
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Windows;
    using System.Windows.Controls;

    /// <summary>
    /// A StackPanel of ListBoxes where each ListBox represents a column of data which can be databound.
    /// </summary>
    /// <remarks>
    /// DataBinding works by mirroring the StackPanel of ListBoxes with an ObservableCollection of ObservableCollections.
    /// </remarks>
    public class SimpleTable : StackPanel
    {                  
        #region Constructors

        static SimpleTable()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(SimpleTable), new FrameworkPropertyMetadata(typeof(SimpleTable)));
        }

        #endregion

        #region Properties

        #region Dependency Properties

        public ObservableCollection<KeyValuePair<string, ObservableCollection<object>>> TableSource
        {
            get { return (ObservableCollection<KeyValuePair<string, ObservableCollection<object>>>)GetValue(TableSourceProperty); }
            set
            {
                var oldValue = this.TableSource;
                if (oldValue != null)
                    oldValue.CollectionChanged -= new System.Collections.Specialized.NotifyCollectionChangedEventHandler(table_CollectionChanged);

                value.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(table_CollectionChanged);
                SetValue(TableSourceProperty, value);
            }
        }
        public static readonly DependencyProperty TableSourceProperty = DependencyProperty.Register(
            "TableSource", typeof(ObservableCollection<KeyValuePair<string, ObservableCollection<object>>>), typeof(SimpleTable),
            new PropertyMetadata(
                new PropertyChangedCallback(OnTableSourceChanged)));

        public Style ListBoxStyle
        {
            get { return (Style)GetValue(ListBoxStyleProperty); }
            set { SetValue(ListBoxStyleProperty, value); }
        }        
        public static readonly DependencyProperty ListBoxStyleProperty =
            DependencyProperty.Register("ListBoxStyle", typeof(Style), typeof(SimpleTable), new UIPropertyMetadata(null));
        
        public DataTemplate ItemDataTemplate
        {
            get { return (DataTemplate)GetValue(ItemDataTemplateProperty); }
            set { SetValue(ItemDataTemplateProperty, value); }
        }
        public static readonly DependencyProperty ItemDataTemplateProperty =
            DependencyProperty.Register("ItemDataTemplate", typeof(DataTemplate), typeof(SimpleTable), new UIPropertyMetadata(null));

        #endregion

        #endregion

        #region Methods

        public void ReinitializeTableSource(ObservableCollection<KeyValuePair<string, ObservableCollection<object>>> table)
        {
            // Detach handlers and clear.
            //
            foreach(ListBox l in this.Children)
                l.LostFocus -= new RoutedEventHandler(listBox_LostFocus);
            //
            this.Children.Clear();
            
            if (table == null)
                return;

            foreach (var columnList in table)
            {
                string columnName = columnList.Key;
                
                var listBox = new ListBox();

                listBox.LostFocus += new RoutedEventHandler(listBox_LostFocus);

                if (this.ItemDataTemplate != null)
                    listBox.ItemTemplate = this.ItemDataTemplate;

                if (this.ListBoxStyle != null)
                    listBox.Style = this.ListBoxStyle;

                listBox.ItemsSource = columnList.Value;
                
                this.Children.Add(listBox);
            }
        }

        private void listBox_LostFocus(object sender, RoutedEventArgs e)
        {
            var listBox = (ListBox)sender;
            listBox.SelectedItem = null;
        }

        private void table_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            ReinitializeTableSource((ObservableCollection<KeyValuePair<string, ObservableCollection<object>>>)sender);
        }

        /// <summary>
        /// Reads properties and fires off the state changer using VSM.
        /// </summary>
        /// <param name="useTransitions">Whether to trigger animations between the states.</param>
        private void UpdateStates(bool useTransitions)
        {
            //if (this.IsFocused)
            //    VisualStateManager.GoToState(this, Ticker.FocusedStateName, useTransitions);
            //else
            //    VisualStateManager.GoToState(this, Ticker.UnfocusedStateName, useTransitions);
        }

        private static void OnTableSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            var newTable = (ObservableCollection<KeyValuePair<string, ObservableCollection<object>>>)args.NewValue;
            var control = (SimpleTable)sender;

            control.ReinitializeTableSource(newTable);
        }
        
        #endregion

        #region Control Overrides

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Grab named ControlTemplate assets and stick em in properties.
            //           
            //this.ScrollViewerElement = GetTemplateChild(SimpleTable.ScrollViewerPartName) as ScrollViewer;
            
            this.UpdateStates(false);
        }
        
        #endregion
    }
}

Starting at the top, it has no parts. This is because it derives from StackPanel and so it is just itself. It's just a panel.

Moving down the code, there are three DependencyProperty members. The first is the DataTable property which is typed as ObservableCollection<KeyValuePair<string, ObservableCollection<object>>>, the second is ListBoxStyle which accepts a Style object which allows such things as removing the default black border around each element and then there's ItemDataTemplate which is a DataTemplate to apply to each of the listboxes.

It's likely that each ListBox will need its own different DataTemplate but it would be quite trivial to add a property that takes a DataTemplateSelector which can use your own logic to work out which DataTemplate to apply to which items.

The next interesting thing is the ReinitializeTableSource method. This kicks out all the old ListBox instances in the panel (itself) and then adds new ones. It copies over the ItemDataTemplate and ListBoxStyle to each new ListBox and also hooks up the data binding to each collection that represents column data.

It also sets an event handler for the lostfocus event which simply removes the selection, otherwise each independant listbox will have its own selection highlight. There might be situations when this is useful, but not today.

Further down there's the handler for the main outer collection CollectionChanged event which can only really occur when columns are added or removed and triggers the whole thing to be reconfigured.

OnTableSourceChanged handles changes to the TableSource dependency property which could itself be bound, this also trigger a complete reconfig.

The rest is left over from the parts and states model and can support the VisualStateManager paradigm.

Basic Model

The most simple use of the control looks like this.

SimpleTable UI

The two buttons manipulate a window level private collection of collections which is communicated via the magic of data binding to the table on screen.

The XAML for the window is here:

<Window x:Class="WpfSimpleTableControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfSimpleTableControl"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<local:SimpleTable x:Name="SimpleTable" Margin="20" Orientation="Horizontal" Height="200">
<local:SimpleTable.ListBoxStyle>
<Style TargetType="{x:Type ListBox}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Width" Value="150" />
</Style>
</local:SimpleTable.ListBoxStyle>
</local:SimpleTable>
<Button Width="150" Click="Button_Click" Margin="2">Add to column 2</Button>
<Button Width="150" Click="Button_Click" Margin="2">Add new column</Button>
</StackPanel>
</Grid>
</Window>

And the codebehind, which does not use MVVM for this simple demo, goes like this.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace WpfSimpleTableControl
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private ObservableCollection<KeyValuePair<string, ObservableCollection<object>>> _table 
            = new ObservableCollection<KeyValuePair<string, ObservableCollection<object>>>();

        public MainWindow()
        {
            this.Initialized += new EventHandler(MainWindow_Initialized);

            InitializeComponent();
        }

        void MainWindow_Initialized(object sender, EventArgs e)
        {
            var list = new ObservableCollection<object>();
            list.Add("Hello");
            list.Add("...world!");
            _table.Add(new KeyValuePair<string, ObservableCollection<object>>("Column1", list));

            list = new ObservableCollection<object>();
            list.Add("Latika!");            
            _table.Add(new KeyValuePair<string, ObservableCollection<object>>("Column2", list));


            this.SimpleTable.TableSource = _table;

            //this.SimpleTable.Background = Brushes.Gray;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var b = (Button)sender;

            switch (b.Content.ToString())
            {
                case "Add to column 2":
                    _table[1].Value.Add(DateTime.Now.Millisecond + b.Content.ToString());
                    break;

                case "Add new column":
                    _table.Add(new KeyValuePair<string,ObservableCollection<object>>("New Column"new ObservableCollection<object>()));
                    _table.Last().Value.Add("Default item.");
                    break;
            }
        }
    }
}

And with MVVM would really just be a binding in the XAML to a ObservableCollection<KeyValuePair<string, ObservableCollection<object>>> typed property on the ViewModel class.

Because this was designed with MVVM in mind, the clicking of items is not considered in the panel. Usually, the DataTemplate for the item binds a Command to a property on the item's ViewModel and so actions go direct.

I will return when I have made it look nice, added a DataTemplate and probably after discovering some minor nuance which completely trounces the whole thing's feasiblity and makes me look a tit.

Hopefully not though, maybe sometimes WPF can be simple. Maybe.

Labels: , , , , ,

Empty DataContract Causes Misleading Error

Wednesday, February 17, 2010 / Posted by Luke Puplett / comments (0)

After my last post on this subject you'd have thought I'd learnt a lesson but the error message and sheer coincidence of the error being about a class I happened to be working on at the time meant it took me 10 hours to unearth the problem.

After updating a service reference that had been left for some months, I received the following IDE warning:

Custom tool warning: Cannot import wsdl:portType

Detail: An exception was thrown while running a WSDL import extension: System.ServiceModel.Description.DataContractSerializerMessageContractImporter

Error: Referenced type 'Steelcore.Spanware.QueryBlock, Steelcore.Spanware, Version=2.0.1.0, Culture=neutral, PublicKeyToken=80726da9f797f65e' with data contract name 'QueryBlock' in namespace 'http://schemas.evoq.co.uk/steelcore/2009/03/spanware' cannot be used since it does not match imported DataContract. Need to exclude this type from referenced types.

Because I had just been adding new web methods that use QueryBlock, and because I am working in Visual Studio 2010 RC1 it was difficult to know if this was a genuine error, afterall, I'd not changed the Steelcore code for ages and it used to work.

Troubleshooting

I'll spare you the details on all the stuff I tried that didn't work. Getting to the bottom of it was a case of isolation.

To remove the local client project from the equation I created a new WCF Service app in Visual Studio, added the reference to my Steelcore library and configured the service with one web method/operation contract that takes the offending class QueryBlock as its sole argument. I then created a new client app, referenced Steelcore and the WCF service. This caused the same warning.

I then opened the Steelcore solution and commented out some of the DataMember attributes on the QueryBlock and rebuilt. Went back to my little test rig and rebuilt and updated the service references. Warning gone.

Obviously one of the types used in the now defunct DataMembers is causing the issue, probably my LooseTypedCriteria class, so I changed the WCF service to accept one of these as its sole argument and found the warning from SvcUtil was talking about Steelcore.Spanware.Criteria.

I commented out some of the DataMembers on Criteria and repeated the process. It all referenced fine, and so again, one of those members is using a class that SvcUtil doesn't like. I noticed one was an enum and then I recalled my enum DataContract problems.

Solution

Sure enough this enum had a DataContract but no DataMember attributes applied. This makes it effectively empty. As before, the solution was to remove the DataContract and let .NET figure out the contract automatically.

Labels: ,

Generic Range<T> class

Friday, February 12, 2010 / Posted by Luke Puplett / comments (0)

Here is my Range class used a project dealing heavily with time slots. A skinny post this as its in response to a request for example generic types from a discussion on LinkedIn.

Sorry about line wrapping.


namespace RootNs.ExampleCorp.Core
{
    using System;
    using System.Runtime.Serialization;

    /// <summary>
    /// Represents a range of some data type.
    /// </summary>
    /// <typeparam name="T">The type of data such as DateTime.</typeparam>
    [DataContract(Namespace = "http://schemas.ExampleCorp.tv/2008/11/app/entities/core")]
    public class Range<T> : System.ComponentModel.INotifyPropertyChanged where T: IComparable<T>
    {
        #region Constructors

        protected Range() { }

        public Range(T start, T stop)
        {
            _start = start; _stop = stop;
        }

        #endregion

        #region OnMethods

        /// <summary>
        /// Is called upon any property change.
        /// </summary>
        /// <param name="e">Args passed through to the PropertyChanged event.</param>
        protected virtual void OnPropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            //
            // Actually nothing here - this is more for derived types to override.

            this.RaisePropertyChanged(e);
        }

        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>        
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design""CA1030:UseEventsWhereAppropriate")]
        protected void RaisePropertyChanged(System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (this.PropertyChanged != null) { this.PropertyChanged(this, e); }
        }

        #endregion

        #region Properties

        [DataMember(Order = 1)]
        public T Start 
        {
            get
            {
                return _start;
            }
            protected internal set
            {
                _start = value;
                this.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("Start"));
            }
        } private T _start;

        [DataMember(Order = 2)]
        public T Stop
        {
            get
            {
                return _stop;
            }
            protected internal set
            {
                _stop = value;
                this.OnPropertyChanged(new System.ComponentModel.PropertyChangedEventArgs("Stop"));
            }
        } private T _stop;

        #endregion

        #region Methods

        public bool IsOverlapping(Range<T> range) { return this.IsOverlapping(range.Start, range.Stop); }

        public bool IsOverlapping(T start, T stop)
        {
            return ((this.Start.CompareTo(stop) < 0) && (start.CompareTo(this.Stop) < 0));
        }

        public bool IsOverlapping(T spot)
        {
            return this.IsEnveloping(spot);
        }

        public bool IsEnveloping(Range<T> range) { return this.IsEnveloping(range.Start, range.Stop); }

        public bool IsEnveloping(T spot)
        {
            return ((this.Start.CompareTo(spot) <= 0) && spot.CompareTo(this.Stop) <= 0);
        }            

        public bool IsEnveloping(T start, T stop)
        {
            return ((this.Start.CompareTo(start) <= 0) && (stop.CompareTo(this.Stop) <= 0));
        }

        #endregion

        #region INotifyPropertyChanged Members

        public event System.ComponentModel.PropertyChangedEventHandler  PropertyChanged;

        #endregion

        #region Object Overrides

        /// <summary>
        /// Returns a string representation of the object.
        /// </summary>
        public override string ToString()
        {
            return String.Format("{0} - {1}"this.Start.ToString(), this.Stop.ToString());
        }

        /// <summary>
        /// Compares this with another for equality.
        /// </summary>        
        public override bool Equals(object obj)
        {
            if (obj == null) { return false; }
            Range<T> cast = obj as Range<T>;
            if (cast == null) { return false; }
            return this.Equals(cast);
        }

        /// <summary>
        /// Compares this with another for equality.
        /// </summary>
        public bool Equals(Range<T> comparison)
        {
            if (comparison == null) { return false; }
            return (bool)(comparison.ToString() == this.ToString());
        }

        // static Equals(obj, obj) actually calls the instance.Equals(obj) anyway so no need to implement.

        public override int GetHashCode()
        {
            return this.ToString().GetHashCode();
        }


        #endregion
    }
}

Labels:

Luke’s Guide to the ListBox Control

Tuesday, January 26, 2010 / Posted by Luke Puplett / comments (0)

This post is a copy (I'd cry if it got mauled) of the Community Content I added to the ListBox MSDN article explaining its highly complex model.

Every time I go to sleep I forget this, may I say, overly complicated beast of a control. Here's some community content that might help programmers of a similar disposition.

Right, a ListBox is a bunch of behaviours over the top of an ItemsControl which is essentially a controller for items in a Panel.

It's useful to consider its template which consists of a Border, a ScrollViewer (of which the ScrollViewer has a ScrollContentPresenter) and an ItemsPresenter.

This Panel can be changed via ItemsControl.ItemsPanel but could also be specified in the control template by adding your chosen Panel and setting its IsItemsHost=true which will hook it up to the ItemsControl/ListBox.

The ItemsControl's job is to generate the items according to customizable templates using an ItemContainerGenerator. For a ListBox, this makes ListBoxItem instances which are ContentControls and thus have ContentPresenters in their templates. Their look is affected using...

ItemTemplate. Simple except that it should not be used in conjunction with ItemTemplateSelector (which sets a DataTemplateSelector class that returns a DataTemplate when its SelectTemplate method is call and so you can add some kind of logic to determine which one to return).

And we're not done yet because there's also ItemContainerStyle and ItemContainerStyleSelector which do a similar job except that they style the container (ListBoxItem) around the item's actual rendered content (as defined by ItemTemplate).

As for the look of the highlight, I guess that's done as part of the template applied to the item in reaction to ListBoxItem.IsSelected. Probably.

[sigh] Whatever happened to HTML and CSS eh?

Labels: ,

Blend 3.0 and VS 2010 :-(

Saturday, January 23, 2010 / Posted by Luke Puplett / comments (1)

This post was written while I was discovering issues with Blend and VS 2010 Beta 2 and publiched in a rush (I’m on my way out but I want to get this out there). Look see, I haven’t even got the time to correct published.

I’ve hastily posted this from a draft I was working on in Windows Live Writer (loving this app aside from the BR tags google seems to add) because I publicly moaned on Twitter about the fact that Blend and now VS 2010 had gotten slow under the weight of WPF – I ordered a 512Mb DDR3 video card yesterday to try and help it - and a guy named Noah Richards purportedly from the IDE Editor Team in Redmond had his bionic Twitter ears on and heard my wimpering. So I thought I’d publish the raw notes I’d been making for him to read…

lukepuplett

MS built VS2010 on super h/w so its slow for me. I must order a super card to run VS2010. Then I can make s/w my customers think is slow.

noahsmark

@lukepuplett My 1GB/single core VMs beg to differ – there are some people with beefy machines, but most are used for server08r2 + hyper-V.

Blend 3.0

Still no right click copy in code editor.

Isolating controls to work on them is improved.

I think about it all too much instead of getting on and using it.

Don’t trust it not to screw my XAML up. Everything I do, I do tentatively, holding my breath waiting for it to explode. And this is learnt behaviour, for it crashes with such regularity.

I found the sample data feature and read about it but within 20 clicks of using it I had broken it.

InvalidCastException: Unable to cast object of type ‘_SampleData.v7.GuideSearchResultsSampleDataItemCollection’ to type ‘System.Windows.Data.CollectionView’

Of course, the problem is that I don’t care for why this happened or to find out. I just want it to work and when it doesn’t, to tell me why or to prevent me making the action in the first place. And besides, I don’t know how to troubleshoot this because its not my code that’s throwing, its Blend’s magic sample data design-time wiring.

Isn’t Blend aimed at creative people or do modern designers have to learn type theory??

WPF is powerful because it leverages the .NET Framework. But Blend doesn’t work well enough to completely insulate the user from having to know and think in .NET.

Our creative brain is on the right but our logical brain is on the left. As an artist I learned that we work our best when one or the other is dominating the processing.

This page http://www.viewzone.com/bicam.html features a nice table of the left and right brain functions.

Blend doesn’t let my right brain dominate for long enough for me to flow.

Traditionally web development uses a lot of sliced imagery. This is because drawing applications are typically reliable and easy to use and allow the designer to roam freely around her creative mind, playing. Then they cut the image up and sew it onto an HTML chassis and let a programmer merge it with some stuff from a database.

Because its unenjoyable I wonder if it means designers may not refine apps as they find it too painful and think “That’ll do”. The World could end up with slightly worse software because the tools are painful. It’s like how the mobile internet only took off when Apple made the tool enjoyable (iPhone).

In fact, if I’d started this project with XAML and its toolset, I’d have thrown in the towel within weeks. Maybe I’m not cut from UI devigner’s cloth.

As for my problem with the sample data, I won’t use that ever again. I’ll go back to my way of doing it which was to design the component in another project and then cut and paste the XAML over.

Visual Studio 2010 Beta 2

Same problem as in VS 2010 with right-clicking highlighted text removes highlight while context menu is open.

And VS 2010 editing is now painfully slow. This doesn’t surprise me as a WPF programmer because Windows Presentation Foundation is big.

I’m sure I’m not alone in that my first thoughts with WPF were that such an enormously complicated object model must come at a price. Layers of objects with so many members and events flying both up and down a deep visual tree.

Toolbar customization is a backward step. I cannot assign icons or edit tool buttons in place.

Collapsible region highlighting distracts me and the +/- buttons aren’t perfectly lined up, making them take a nanosecond longer to use. Pasting collapsed methods, opens them.

Scrolling doesn’t feel as fast. Parts of the screen don’t draw properly, sometimes the whole VS real-estate flickers on and off and I see my desktop for a brief moment.

Switching between code editor/designer tabs takes ~1 second :(

Mostly its not terrible but the difference that I feel the most is in the text editor, and of course that’s where I spend most of my time. The IntelliSense is a bit slower and it just tips it over to the wrong side of satisfactory.

The hourglass fails to appear in many situations leaving me trying to click things and hovering over buttons to see if the window has frozen.

Some resources failed to resolve, leaving the designer to miss rendering parts of my view. An error in the Error List indicated that an xmlns reference could not be resolved. I removed the reference below:

xmlns:SampleData="clr-namespace:Expression.Blend.SampleData.GuideSearchResultsSampleDataSource"


And then I replaced the reference because I wanted to look further into the issue resolving that Expression reference but undoing my changes led to the issue being resolved. I reloaded the designer for my view and that too all worked, presumably because the resources had now built.



Incidentally, copying and pasting the text above from App.xaml to this blog entry resulted in the App.cs file unexpectedly opening – since uninstalling “Copy from Visual Studio for Windows Live Writer” this seems to have stopped happening.



Loading multiple-projects seems to be a bit faster and they’re sorted A-Z now. Builds seem a bit quicker, particularly if its dependencies were only just built.



And generally it is a nicer place to be that 2008. Visual Studio has been a wonderful piece of software since 2005 and wasn’t ever bad at all. I often wonder if I could ever get on using another major technology stack because I’d miss VS too much.



Remember I’ve posted this without checking it or removing google’s BRs. Here goes nothing.

Flowing Custom Control Values to ViewModel/Bindings

Friday, January 15, 2010 / Posted by Luke Puplett / comments (0)

This article discusses an issue I had when making a custom login box control, particularly the issue of getting the values in the username and password boxes to flow back down to the view-model. The issue might be my design, which could be fundamentally wrong, if only I knew of another way to do it.

The problem with Windows Presentation Foundation and its more promiscuous sister, Silverlight, is that its as good as it is bad. That is, its hard to deny its brilliance when you're carving out complex drawings and alpha gradients, adding subtleties to button animations and then there's the whole hardware accelerated 3D bit. But its bloody complicated because its mandate is to be everything to everyone and when so much is done for you all that stuff has to be massively multifaceted.

My LoginControl uses the parts and states model and the WPFToolkit to get at the VisualStateManager. I won't go into these things as they're documented enough and Karen Corby has a great MIX session video on it, too.

So I have a few parts, two TextBox controls and a Button. You can guess that these are username, password and the logon button.

Firstly, and I'll get this one over and done with quickly: I don't use the PasswordBox control. It does not support data binding because it takes a cautious approach and does things securely. Instead I apply a strikethrough formatting to a regular TextBox within its default style in generic.xaml.

You can see how this is done by reading my comments at the bottom of the MSDN article on the PasswordBox.

Welcome to my LoginControl

Cutting straight to the meat of the implementation, my control has the following properties:

  • private TextBox UsernameTextBox // the property which stores the TextBox UI control part/element for the username
  • private TextBox PasswordTextBox // the property which stores the TextBox UI control part/element for the password
  • public string Username // the dependency property which stores the username that can be bound to from a view model
  • public string Password // ditto but for the password

Because I can't bind my view model to a property of a the LoginControl's username TextBox and I don't want to expose the TextBox controls publicly, I use the following trick to hook up a binding between the TextBox.Text dependency property and LoginControl.Username (ditto for the password):

private TextBox UsernameTextBox
{
    get
    {
        return (TextBox)GetValue(UsernameTextBoxProperty);
    }
    set
    {
        // Detach observer from outgoing value.
        //
        var oldValue = this.UsernameTextBox;
        if (oldValue != null)
        {
            BindingOperations.ClearAllBindings(oldValue);
            oldValue.GotKeyboardFocus -= new KeyboardFocusChangedEventHandler(TextBoxes_GotKeyboardFocus);
            oldValue.KeyUp -= new KeyEventHandler(Username_KeyUp);
        }

        SetValue(UsernameTextBoxProperty, value);

        if (value != null)
        {
            value.SetBinding(TextBox.TextProperty, new Binding("Username")
            {
                Source = this,
                Mode = BindingMode.TwoWay
            });
            value.GotKeyboardFocus += new KeyboardFocusChangedEventHandler(TextBoxes_GotKeyboardFocus);
            value.KeyUp += new KeyEventHandler(Username_KeyUp);
        }
    }
}

And once that's done there's nothing else to consider except that when adding my LoginControl to my XAML page, I need to hook up the binding to the appropriate properties in the view model. That's easy:

Username="{Binding Path=Username, Mode=TwoWay}" Password="{Binding Path=Password, Mode=TwoWay}"

No worries there.

In Practice it Fails

The problem occurs when the login button is clicked and the ICommand is executed. The RelayCommand on the view model kicks in and my Action runs but if I stop the debugger right there I see that my view model does not have up-to-date Username and Password values!

My theory is that the binding system has not done its magic in between my typing of text and clicking of button. And although in theory the LostFocus events upon which data binding updates rely should have been triggered, my daisy-chained setup is somehow ballsing things up. Actually, there is a LostFocus on the username TextBox which should update the LoginControl.Username property, it doesn't, but even if it had, what's the activator to get the value to then jump from the Username property to its source down in my view model? By the way, flows up from source seem to work fine.

Whatever, I don't know. My solution was to grab each of my bindings and manually hit the UpdateSource method from within the login button's click method:

private void LoginButtonElement_Click(object sender, RoutedEventArgs e)
{
    var utbx = this.UsernameTextBox.GetBindingExpression(TextBox.TextProperty);
    utbx.UpdateSource();

    var ptbx = this.PasswordTextBox.GetBindingExpression(TextBox.TextProperty);
    ptbx.UpdateSource();

    var ubx = BindingOperations.GetBindingExpression(this, UsernameProperty);
    var pbx = BindingOperations.GetBindingExpression(this, PasswordProperty);

    ubx.UpdateSource();
    pbx.UpdateSource();

    if (this.PasswordTextBox != null)
    {
        if (!this.PasswordTextBox.IsFocused)
        {
            this.PasswordTextBox.Focus();
            if (!this.TryLoginButton.IsMouseOver)
                return;
        }
    }

    if (this.Command != null)
        this.Command.Execute(new KeyValuePair<stringstring>(this.Username, this.Password));
}

Which brings me right back to the point I made earlier about not doing this the right way. I'm not sure any other way would flow the values in time anyway. But then there's always the question of how everyone else doesn't seem to have this problem...

Labels: , , ,