Rockford Lhotka's Blog

Home | Lhotka.net | CSLA .NET

 Friday, February 26, 2010

Continuing on my quest to use the Visual Studio 2010 XAML designer, MVVM and absolutely no code behind, I ran into the common problem of setting focus to a control as a form loads.

This is easily accomplished using code-behind:

firstControl.Focus();

But of course that requires code-behind, which I believe should be unnecessary.

It can also be accomplished by creating an attached property or (with the Blend 3 SDK) trigger action or behavior. Neither of these are great with the VS10 designer though, because the designer has no way to apply attached properties and doesn’t know about the Blend 3 SDK concepts.

So while those solutions are fine, they require manual typing of XAML, which I believe should be unnecessary (as unrealistic as that view might be…).

My solution is to create a FrameworkElement called SetFocus. This shows up in the Toolbox and so can be dragged directly onto the form using the designer. The SetFocus control has no visual aspect, so it doesn’t interfere with the layout or look of the form. Once SetFocus is on the form, the Properties window can be used to set its TargetProperty property, indicating the property that should get focus when the form is loaded.

image

This requires no code-behind and no manual typing of XAML. It keeps the concept of startup focus completely in the view, which (I think) is a good place for it. So my viewmodel doesn’t know or care about this issue. And the same control works in Silverlight and WPF, which should be the goal for any good XAML control.

Here’s the code:

using System;
using System.Windows;
using System.Windows.Controls;
using System.ComponentModel;

namespace Collective.Controls
{
  /// <summary>
  /// Sets the focus to the TargetControl property as the
  /// form is loaded.
  /// </summary>
  public class SetFocus : FrameworkElement
  {
    /// <summary>
    /// Gets or sets the target UI control.
    /// </summary>
    public static readonly DependencyProperty TargetControlProperty =
      DependencyProperty.Register("TargetControl", typeof(Control),
      typeof(SetFocus), new PropertyMetadata((o, e) =>
      {
        var sf = o as SetFocus;
        if (sf != null && sf.TargetControl != null)
          sf.TargetControl.Focus();
      }));
    /// <summary>
    /// Gets or sets the target UI control.
    /// </summary>
    [Category("Common")]
    public Control TargetControl
    {
      get { return (Control)GetValue(TargetControlProperty); }
      set { SetValue(TargetControlProperty, value); }
    }
  }
}

Friday, February 26, 2010 11:25:09 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [8]  | 
 Friday, February 19, 2010

As noted in a previous post, I’ve been spending some quality time with the new VS10 XAML designer. It is fun, but challenging to figure out how the designer wants everything to work, and to adapt existing CSLA .NET components to work smoothly in the new environment.

Yesterday I spent a substantial amount of time trying to get PropertyStatus, InvokeMethod and Execute to work nicely. These three controls have one thing in common: they all interact directly with the real data object. InvokeMethod and Execute both invoke methods on the data object (usually a viewmodel), and PropertyStatus reads property-specific metadata about a property, such as whether the property has any info/warning/error messages that the user should see.

The VS10 designer uses something called a CollectionViewSource as its primary binding target. When you drag-and-drop a type from the Data Sources window onto your form, the designer creates a CVS resource in the form and uses that as the DataContext for your UI elements. In this regard it is not entirely unlike a data provider control in WPF, though it is certainly not a data provider control.

If you are using a viewmodel (and I am), then your DataContext is actually a hierarchy of object instances.

image

This is made slightly more complex when you realize that the Source property of a CollectionViewSource can only be a list. So really the CVS contains a list of viewmodel objects – but in my case that list has only one item.

InvokeMethod and Execute both need access to the viewmodel – that’s the object containing the methods to be invoked. PropertyStatus actually needs access to the Model (the real business object), because that’s the object where the business properties and associated metadata are actually implemented.

None of this would be that complex, except that the VS10 designer sets the DataContext to the CVS, and then uses currency to make one item in the Source property “current”. But if you, in code, get access to the DataContext for a control you get the CVS. If you get a BindingExpression object for a given binding, the DataItem is the current item in the CVS, which turns out to not be the viewmodel. Instead the DataItem on a binding is an internal type within .NET.

And this is what had me stuck. The CVS has no CurrentItem property, but this internal type does. However (short of reflection), there’s no way to interact with members of an internal type. What to do? Thanks to help from Mark Boulter I now know that this internal type implements the public ICollectionView interface, and that interface does have a CurrentItem property.

Whew!

What this means is that in InvokeMethod and Execute, I can take the associated UI control (like a Button or something) and use it to find the viewmodel:

public static object GetTarget(UIElement ctrl)
{
  object result = null;
  result = ctrl.GetValue(TargetProperty);
  if (result == null)
  {
    var fe = ctrl as FrameworkElement;
    if (fe != null)
      result = fe.DataContext;
  }
  var icv = result as ICollectionView;
  if (icv != null)
    result = icv.CurrentItem;
  return result;
}

This basically takes the DataContext value, sees if it is an ICollectionView, and if so it returns the CurrentItem – which is the real data object, or at least the viewmodel.

PropertyStatus is a little more complex, because it has to trace down to the actual underlying business object, and if so it needs to dereference elements of the property name path. For example, the property name might be Name, or it might be Model.Name. If it is Name, the assumption is that CurrentItem is the actual data object. If it is something like Model.Name, the assumption is that PropertyStatus needs to use the Name property on the object referenced by the Model property. The code actually goes n levels like this, allowing for access deep into hierarchical object graphs.

private object GetRealSource(object source, string bindingPath)
{
  var icv = source as ICollectionView;
  if (icv != null)
    source = icv.CurrentItem;
  if (source != null && bindingPath.IndexOf('.') > 0)
  {
    var firstProperty = bindingPath.Substring(0, bindingPath.IndexOf('.'));
    var p = MethodCaller.GetProperty(source.GetType(), firstProperty);
    return GetRealSource(
      MethodCaller.GetPropertyValue(source, p),
      bindingPath.Substring(bindingPath.IndexOf('.') + 1));
  }
  else
    return source;
}

Again, if the source is an ICollectionView, the code skips right to the CurrentItem property, which is the first “real” object worth using.

I suppose writing controls that interact with the real data object isn’t something people do every day, but if you end up writing such a control hopefully this blog post will help save you some time and frustration.

Friday, February 19, 2010 2:42:23 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Thursday, February 18, 2010

OK, this is cool. Magenic is running a Silverlight/WPF development contest, open to anyone. The primary prizes for first to third place are sizable donations to the Haiti relief effort, with respectable cash prizes for the winning teams.

Even if your visual UI design skills aren’t super-solid (like mine), all teams get an hour of Magenic Studio’s assistance, which might at least jump-start a decent looking visual layout :)

The app must use Silverlight or WPF (versions 3 or 4 are both acceptable), otherwise pretty much anything goes. Well, obviously the judges need to be able to run the app and view the code, but otherwise…

The contest starts on Feb 22 and ends March 22.

So go to the web site, read the rules, send email to get registered and start exercising those mad XAML and .NET skilz!!

Thursday, February 18, 2010 10:07:32 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Tuesday, February 16, 2010

Visual Studio 2010 includes a new XAML designer, with capabilities roughly comparable to the Windows Forms designer we’ve enjoyed all these many years.

This means you can use drag-and-drop design concepts to get controls from the Toolbox onto the design surface, drag them around, see snaplines and use the Properties window to set properties (including data binding). And the Data Sources window works, so you can drag entire objects or specific properties from a data source (like a CSLA .NET business object) onto the design surface to create a UI control with the data binding all set up.

image

I’m very excited about this designer, as I think the lack of a good designer has been the single biggest roadblock to WPF (and now Silverlight) adoption. While some people are OK with manually typing XAML, I think a lot more people have been (im)patiently waiting for tooling so they don’t have to work at that level. I’m in the latter camp, but I’ve been forcing myself to type XAML for a few years now because the results are so much better than Windows Forms that it has been worth the pain.

However, with the designer being out now (stable and fast in the RC) I’ve been spending some quality time with it. And what I’m finding is that I have to unlearn a lot of techniques. Things that work great when you manually type XAML don’t always translate exactly into the designer metaphor and/or workflow.

CollectionViewSource Indirection

For example, when you drag something from the Data Sources window to the design surface the designer creates a CollectionViewSource resource to manage the data object. Suppose the data source is your business object – the CVS now contains your business object. Suppose the data source is your viewmodel object – the CVS now contains your viewmodel object.

It is this last point that is tricky. I have been building my viewmodel objects so they are the resource. Now they are contained inside the resource. There’s a new level of indirection involved.

Oh, and the CVS only contains a list of objects, never a single object. So when I drag my single viewmodel onto the designer it ends up in a list of 1 item in the CVS. And that’s OK, it just means your binding expressions need to take this into account. Instead of setting the DataContext directly to the resource, you must set it to the current item in the resource:

DataContext=”{Binding Source={StaticResource viewModelViewSource}, Path=/}”

image It is the Path=/ part that is new and different. Of course you don’t really type that binding expression – the designer has a dialog where you set up or edit the DataContext and other binding details.

In the screen shot to the left you can see some regions on the form. The top, where the button and checkbox are visible, is bound to the / path. The bottom, where the datagrid is visible, is bound to the Model property – which means the datagrid is bound directly to the business object, and that’s a fine thing since CSLA 4 BusinessListBase objects derive from ObservableCollection<T> and are fully bindable in XAML.

Executing ViewModel Verbs

The other area of challenge I’ve encountered is with triggering viewmodel verbs/commands/actions/methods when a UI event occurs. Remember that Silverlight doesn’t have commanding, and even in WPF commanding is not a complete solution to this problem.

Blend 3 introduced the concept of trigger actions and behaviors, and CSLA .NET 3.8 has an Execute trigger action that executes a method on the viewmodel when an event occurs on a UI control. Prior to that, in version 3.6, there was the InvokeMethod attached property that did the same basic thing.

The problem with both InvokeMethod and the Blend 3 trigger action concept is that there’s no access to these concepts from the VS10 designer. You can use them, but you have to manually type the XAML. My personal goal and hope with the designer is to never type (or even see) XAML.

image So this evening I took the ideas from InvokeMethod and Execute and created a new control I’m calling TriggerAction (see code here). The TriggerAction control is a real control – it shows up in the Toolbox and you can drag it onto the design surface. It is invisible (collapsed), as it really has no UI aspect. But it does have some important properties:

  • TargetControl – use element binding to connect it to the UI control that’ll raise the event
  • TriggerEvent – the name of the event raised by the UI control
  • MethodName – the name of the method/verb/action on the viewmodel (DataContext) object to invoke in response to the trigger event
  • MethodParameter – a bindable parameter value that can optionally be passed to the viewmodel method

In short, TriggerAction does exactly what InvokeMethod and Execute do, but in a way that is friendly to the XAML designer. This means not requiring any manual editing of XAML.

I’m not done yet, but I’m becoming increasingly convinced that the VS10 designer really will allow creation of XAML interfaces using patterns like MVVM and requiring zero code-behind the XAML – without the developer ever having to see (much less edit) XAML directly. Happiness!!

Tuesday, February 16, 2010 8:48:16 PM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
 Monday, February 01, 2010

CSLA .NET 3.8.2 is now available for Windows and Silverlight

This is a bug-fix release, and has been in beta for several months.

There is only one real functional (and breaking) change, which affects only people building WPF or Silverlight applications and using the design-time data support built into the data portal. This feature has been disabled in 3.8.2 because there’s an issue in WPF (and possibly Silverlight) where detecting design time vs runtime in a high volume runtime scenario would cause a Win32 exception in the .NET runtime.

Since Visual Studio 2010 and .NET 4.0 have a different way of handling design time data, this was going to change anyway; but making this change now helps avoid this WPF runtime issue, and so was important.

Monday, February 01, 2010 11:35:17 AM (Central Standard Time, UTC-06:00)  #    Disclaimer  |  Comments [0]  | 
On this page....
Search
Archives
Feed your aggregator (RSS 2.0)
August, 2014 (2)
July, 2014 (3)
June, 2014 (4)
May, 2014 (2)
April, 2014 (6)
March, 2014 (4)
February, 2014 (4)
January, 2014 (2)
December, 2013 (3)
October, 2013 (3)
August, 2013 (5)
July, 2013 (2)
May, 2013 (3)
April, 2013 (2)
March, 2013 (3)
February, 2013 (7)
January, 2013 (4)
December, 2012 (3)
November, 2012 (3)
October, 2012 (7)
September, 2012 (1)
August, 2012 (4)
July, 2012 (3)
June, 2012 (5)
May, 2012 (4)
April, 2012 (6)
March, 2012 (10)
February, 2012 (2)
January, 2012 (2)
December, 2011 (4)
November, 2011 (6)
October, 2011 (14)
September, 2011 (5)
August, 2011 (3)
June, 2011 (2)
May, 2011 (1)
April, 2011 (3)
March, 2011 (6)
February, 2011 (3)
January, 2011 (6)
December, 2010 (3)
November, 2010 (8)
October, 2010 (6)
September, 2010 (6)
August, 2010 (7)
July, 2010 (8)
June, 2010 (6)
May, 2010 (8)
April, 2010 (13)
March, 2010 (7)
February, 2010 (5)
January, 2010 (9)
December, 2009 (6)
November, 2009 (8)
October, 2009 (11)
September, 2009 (5)
August, 2009 (5)
July, 2009 (10)
June, 2009 (5)
May, 2009 (7)
April, 2009 (7)
March, 2009 (11)
February, 2009 (6)
January, 2009 (9)
December, 2008 (5)
November, 2008 (4)
October, 2008 (7)
September, 2008 (8)
August, 2008 (11)
July, 2008 (11)
June, 2008 (10)
May, 2008 (6)
April, 2008 (8)
March, 2008 (9)
February, 2008 (6)
January, 2008 (6)
December, 2007 (6)
November, 2007 (9)
October, 2007 (7)
September, 2007 (5)
August, 2007 (8)
July, 2007 (6)
June, 2007 (8)
May, 2007 (7)
April, 2007 (9)
March, 2007 (8)
February, 2007 (5)
January, 2007 (9)
December, 2006 (4)
November, 2006 (3)
October, 2006 (4)
September, 2006 (9)
August, 2006 (4)
July, 2006 (9)
June, 2006 (4)
May, 2006 (10)
April, 2006 (4)
March, 2006 (11)
February, 2006 (3)
January, 2006 (13)
December, 2005 (6)
November, 2005 (7)
October, 2005 (4)
September, 2005 (9)
August, 2005 (6)
July, 2005 (7)
June, 2005 (5)
May, 2005 (4)
April, 2005 (7)
March, 2005 (16)
February, 2005 (17)
January, 2005 (17)
December, 2004 (13)
November, 2004 (7)
October, 2004 (14)
September, 2004 (11)
August, 2004 (7)
July, 2004 (3)
June, 2004 (6)
May, 2004 (3)
April, 2004 (2)
March, 2004 (1)
February, 2004 (5)
Categories
About

Powered by: newtelligence dasBlog 2.0.7226.0

Disclaimer
The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.

© Copyright 2014, Marimer LLC

Send mail to the author(s) E-mail



Sign In