Windows 8 WinRT/Metro Missing UpdateSourceTrigger

If you’ve done WPF or Silverlight programming, you may have found an occasion where using the Binding property UpdateSourceTrigger set to PropertyChanged was extremely useful. (I know I have!)

It may have looked something like this:

<TextBox Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}" />

The key feature was the live updating of the property through the binding. For example, as the user typed in the TextBox above, the property Value in the bound object would have been updated as the user typed. The default behavior in Silverlight 5 and WPF is to use LostFocus: the Value is only updated when the TextBox loses focus. Sometimes, that’s OK.

In WinRT/Metro however, this option was completely removed, and no decent replacement was provided unfortunately. In searching for a reasonable work-around, I discovered a few other related missing pieces from WinRT/XAML:

  • BindingOptions.GetBinding: This allowed the WPF programmer a method for retrieving a Binding object. I was interested in this option as I wanted to clone the original Binding, make it two-way, and attach to the TextChanged event so that a new binding could be updated when the text changed, with the new binding pointing to the same property as the Text property. (My solution uses the basic idea as this).
  • FrameworkElement.GetBindingExpression: Again, gone. Same basic logic as above. (Probably uses same underlying support that just isn’t there).
  • XamlWriter.Save: XamlWriter is completely gone. I thought maybe I’d save an instance of the TextBox to Xaml, and create a new one with adjusted bindings from the Xaml. XamlReader does exist.

You can start to feel how Xaml really is just a thin wrapper on the Windows 8 run time as you start to look around to see what remains from WPF. Even traditional Silverlight features are gone unfortunately. Maybe Windows 9? Smile

So, I took a round-about approach to solving this problem.

 public class UpdateSourceHelper : FrameworkElement
 {
     public static string GetUpdateSourceText(DependencyObject obj)
     {
         return (string)obj.GetValue(UpdateSourceTextProperty);
     }

     public static void SetUpdateSourceText(DependencyObject obj, string value)
     {
         obj.SetValue(UpdateSourceTextProperty, value);
     }

     // Using a DependencyProperty as the backing store for UpdateSourceText.  This enables animation, styling, binding, etc...
     public static readonly DependencyProperty UpdateSourceTextProperty =
         DependencyProperty.RegisterAttached("UpdateSourceText", typeof(string), typeof(UpdateSourceHelper), new PropertyMetadata(""));
     
     public static bool GetIsEnabled(DependencyObject obj)
     {
         return (bool)obj.GetValue(IsEnabledProperty);
     }

     public static void SetIsEnabled(DependencyObject obj, bool value)
     {
         obj.SetValue(IsEnabledProperty, value);
     }

     // Using a DependencyProperty as the backing store for IsEnabled.  This enables animation, styling, binding, etc...
     public static readonly DependencyProperty IsEnabledProperty =
         DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(UpdateSourceHelper), 
             new PropertyMetadata(false, 
             // property changed
             (obj, args)=> 
             {                    
                 if (obj is TextBox)
                 {
                     TextBox tb = (TextBox)obj;
                     if ((bool)args.NewValue)
                     {                                                                                      
                         tb.TextChanged += AttachedTextBoxTextChanged;
                     }
                     else
                     {
                         tb.TextChanged -= AttachedTextBoxTextChanged;
                     }
                 }
             }
         ));

     static void AttachedTextBoxTextChanged(object sender, TextChangedEventArgs e)
     {
         if (sender is TextBox)
         {
             var tb = (TextBox)sender;
             tb.SetValue(UpdateSourceHelper.UpdateSourceTextProperty, tb.Text);                
         }
     }        
 }

And here it is in use:

xmlns:local="using:WiredPrairie.Converter"

(The above namespace is needed to provide context to the Xaml parser for the attached properties. Change it to whatever you need).

<TextBox Height="Auto" Margin="0,6" Grid.Row="1" TextWrapping="Wrap" TabIndex="0" 
         Text="{Binding Value}" 
         local:UpdateSourceHelper.IsEnabled="True" 
         local:UpdateSourceHelper.UpdateSourceText="{Binding Value, Mode=TwoWay}"/>

A classic pattern in WPF for extending built in controls without subclassing was to create an attached behavior. Thankfully, this pattern still works.

The core concept is to attach to the original instance of the TextBox by using an enabling property. In this case, an attached property  called IsEnabled is added to the TextBox.

When the value of the IsEnabled property changes from the default of false, the PropertyChanged callback is called. It’s here that the simple magic happens. As long as the attached property is attached to an instance of TextBox, it wires up to the TextChanged event.

In the TextChanged event handler, the value of the attached TextBox (Text property) is set into the second binding’s value. By doing this, it then sets the datasource’s object’s bound property to the new value immediately.

The annoying part about this solution really is the extra binding that is required. As there isn’t a documented/known API for reading an existing binding, this technique requires that the developer specify the binding twice.

local:UpdateSourceHelper.UpdateSourceText="{Binding Value, Mode=TwoWay}"

In WPF, it would have been possible to declare the UpdateSourceText as defaulting to TwoWay binding. But, again, that feature has been removed. Without the TwoWay binding mode, the SetValue call in the TextChanged event handler will not update the object’s bound property. It’s one way.

I looked low and high for a more elegant solution, but couldn’t find one. If you find something better, please leave a comment!

Lovin’ .NET 4.5’s CallerMemberNameAttribute

Check this out:

http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx

Hate the INotifyPropertyChanged pattern in .NET, and especially the syntax of needing to pass the name of the property being changed (via a string or an Expression or …)?

There’s finally a built-in solution in .NET 4.5!

private string _testValue;
public string TestValue
{
    get { return _testValue; }
    set {
        if (_testValue != value)
        {
            _testValue = value;
            RaisePropertyChanged();
        }            
    }
}

protected void RaisePropertyChanged([CallerMemberName] String propertyName = null)
{
    var eventHandler = this.PropertyChanged;
    if (eventHandler != null)
    {
        eventHandler(this, new PropertyChangedEventArgs(propertyName));
    }
}

A new attribute you can apply to optional parameters which emits the name of the Caller! So in the example above, it’s “TestValue.”

Rock on!

Simple Node based Http Put simulator

It’s great what you can accomplish in a few lines of code. The Node based source code below uses express to create a mini view-based web server along with a mock Http Put file upload destination.


/**
* Module dependencies.
*/

var express = require('express')
, routes = require('./routes')
, http = require('http')
, path = require('path');

var app = express();

app.configure(function(){
app.set('port', process.env.PORT || 8080);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
});

app.put('/upload/:fileUpload', function(req, res, next){
console.log('uploading!');
res.send('OK', { 'Content-Type': 'text/plain' }, 200);
});

app.configure('development', function(){
app.use(express.errorHandler());
});

app.get('/', routes.index);

http.createServer(app).listen(app.get('port'), function(){
console.log("Express server listening on port " + app.get('port'));
});

Using WebStorm’s Node/Express template, I only added 4 lines:

app.put('/upload/:fileUpload', function(req, res, next){
console.log('uploading!');
res.send('OK', { 'Content-Type': 'text/plain' }, 200);
});

These 4 lines added a new route/path for uploading a file in the pattern of /upload/ {fileName}.

The response was “OK.”

I’d tried this same thing using ASP.NET MVC 4 (with Razor), but was stumped by the error when trying to use HttpPut on an Action in the controller.

I was using this end-point as a mock-upload destination for my SmugMug application, SnugUp. I wanted to eliminate the uploading process as it’s time consuming and messes up my SmugMug galleries! Smile

The C# code is simple as it uses HttpWebRequest:

HttpWebRequest uploadRequest = (HttpWebRequest)WebRequest.Create(ApplicationConstants.UploadUrl + "photo.jpg");

uploadRequest.Timeout = (int)10080 * 60 * 1000; // 7 days
uploadRequest.Method = "PUT";
uploadRequest.UserAgent = ApplicationConstants.UserAgent;
uploadRequest.ContentLength = fi.Length;
uploadRequest.KeepAlive = true;

(SmugMug grabs the file name from a custom Http Header, so, the put URL is always “photo.jpg”.)

How to determine actual costs of Verizon’s Family SharePlan (with a discount)

Two chat sessions with Verizon’s customer service folks didn’t give a clear picture of the actual costs of the new Family SharePlan when a corporate discount was applied.

Currently, I pay around $153 a month and have a 15% discount applied.

image

The first chat session with Deborah suggested that the discount I was currently receiving (15%) would apply to the entire plan.

image

I really appreciated the pushy “buy some accessories” at the end of the chat. I hadn’t thought to buy accessories!

The second chat session I had suggested however that the discount would only apply to the data plan (I’d had some doubts about the reliability of the first answer due to some forum posts):

image

Daphne actually stopped chatting with me – apparently I wasn’t important enough, or she was just trying to gather what accessories I should buy ….

So, here’s how I actually determined the real cost:

image

image

image

image

Click the “Calculate” button.

image

image

The new cost was higher than what we currently pay.

And to be very clear, the discounts typically only apply to the data portion, but you should check using the technique above to be certain before making a switch.

Do your support organization a favor: create better error messages!

From Adobe Illustrator CS6:

SNAGHTML11cdf257

While it’s possible that Adobe has a codified error database for “CANT” … it would seem more likely that someone will need to search through source code to find out what operation cannot complete.

(To fix the above problem, I reset the settings for Illustrator by running as admin and holding down CTRL+SHIFT+ALT. Illustrator starts in a weird mostly not working state, but after shutting down that instance and restarting normally, the error went away).

This one is slightly better:

SNAGHTML11cffc55

Although, in a clean install of CS6, I don’t know why I’m getting this error.

Photoshop upon first run was giving me an error message:

Could not open a scratch file because the file is locked, you do not have necessary access permissions, or another program is using the file. Use the "Properties" command in the Windows Explorer to unlock the file.

You’ll note that it mentions a file being locked, but no clear action to take because of this issue.

Here’s what apparently is one cause of this error:

A non-default installation of Windows (or Macs), where your temporary and user folders are not on the installation disk for Photoshop. In my case, I have 3 disks in my computer:

  1. SSD  < Apps installed
  2. 600GB HD  < Users, temporary
  3. 600GB HD < Virtual Machines, backups, etc.

Photoshop (and Illustrator as well actually) try to create the scratch folder on the primary drive, even when they don’t have permission to do so. I changed the setting by:

  1. Run Photoshop as Administrator
  2. Immediately hold CTRL+ALT+SHIFT.
  3. Photoshop will ask if the Settings should be reset. Answer OK.
  4. Go to Edit > Preferences > Performance
  5. Make changes to Scratch Disks:
    SNAGHTML11d999cc[4]
  6. Click OK and Exit Photoshop
  7. Restart normally.