Friday, 27 July 2012

Data-Binding Pitfalls on WPF, WP7, Silverlight and WinRT Part 2

Most of my data binding worries stem from implementing my view model for MVVM.

One of the things I noticed was that it was very difficult to get things right when multiple properties are combined to form another property, so a change to any input would need to raise notifications that the derived property had changed. 

This led to code that was difficult to debug, write and maintain.  In addition we found that in many cases, properties weren’t changed in isolation.  Changing one property on an object often meant others would soon change too.

So, to deal with this issue, we built a class that coalesced property notifications, and also had a representation of dependent properties which would fire when an input changed.  With the addition of a FreezeNotifications and UnfreezeNotifications mechanism, we could queue up all of the changes, and then fire each change only once.

However, we still end up with many properties on the same object potentially firing, and still requiring recalculation of layout multiple times.

In the previous post we were concerned with how data binding worked on a control, and in a control the properties are generally a Dependency Property.  In this example the properties in question simply implement INotifyPropertyChanged, and the objects in question aren’t based on the Control class, so calling Invalidate won’t suffice to defer our work.

In the end, here’s what we did. 

We had several objects that needed to connect to the data to get property notifications.  When a property changed, we marked our object as dirty in such a way we could determine what needed to be updated.

We then fired off a Task, which collected the data from the database, and built up the information required.  In the case where a previous task was already running, we called Cancel on the existing task, and then started a new task to load the newly loaded data.

This worked ok.  The tasks ran on another thread on the multi core machine, but we found that we were cancelling the task about 6 times before finally allowing it to run to completion.

How can I Stop Wasting Cycles?

We thought there had to be a better way.  And that turned out to be our old friend Dispather.BeginInvoke.  Instead of launching the recalculation immediately, we asked for it to happen when we finally got around to processing messages again.

Initially we passed it with the DispatcherPriority.DataBind, to get it into the DataBinding queue.  That indeed worked, and we no longer get any cancellation requests, as change detection all works on the UI thread, and when we finally relinquish control, we then start our task, and mark the object as not dirty.

When subsequent notifications arrive, the work is already done, so no further action is taken.  There’s probably a further improvement to be had by checking that we haven’t already marked the object as dirty, and avoiding queuing the call altogether.

Finally, we wondered if we really needed to drop to that priority level, and the answer was no; Normal priority works just fine.

The Moral of the Story?

Don’t be in a big hurry to do the work, as someone might change their mind, and you’ll just have to start all over again.  Instead, schedule the work to be done using Dispatcher.BeginInvoke. 

But BeginInvoke doesn’t exist on WINRT

It appears that CoreDispatcher.RunAsync gives us 3 priority levels, so changing to this method on WinRT will probably continue to work (if you can manage to get your hands on a CoreDispatcher object in the first place)

Thursday, 26 July 2012

Data-Binding Pitfalls on WPF, WP7, Silverlight and WinRT Part 1

I've been doing a lot of work on all four platforms (WPF, WP7, Silverlight and WinRT), and am puzzled by how data-binding is supposed to be implemented.

For example, in David Anson's Data Visualization toolkit, you can be penalized with an extraordinarily long delay if property changes happen in the wrong order.

The problem in that specific instance is that the control sets the default range to 1 year, but the order of changing properties can cause the problem to occur at any point.  For example, if you were to change a date time axis from a range of 1 day to 1 year and back, with intervals of 1 hour and 1 month respectively, on the way to 1 year, you'd need to set the interval first, and the range second.  In the other direction, it's the opposite.

Now with Data Binding, it's not clear how you'd arrange this.  In my case in WPF everything appeared to be working just fine, until I newed up a control for printing.  Printing worked fine, but PrintPreview had a hugely long delay, before the preview window appeared.  The culprit?  The DateTimeAxis.

It turned out the problem was provoked by my code behind setting the Interval and Interval type.  When I changed it to data-binding, the problem disappeared.

Now, I suspect the reason this fixed the problem has to do with the options on Dispatcher.BeginInvoke and the Dispatcher itself.  If you look there (in WPF) the DispatcherPriorities includes Render and DataBind levels in addition to Normal.  Shawn Wildermuth wrote an article a long time ago about the dispatcher that provides some insights.

If you examine the code in the DateTimeAxis, you’ll see that on a property change, most of what happens is a simple call to Invalidate().  That pushes the refresh down to the Render level, allowing all of the other Invalidate calls to occur before doing any work for changes resulting from property changes.  I’ll come back to that scheduling trick in a part 2 tomorrow.

Now, my code didn’t use data binding for all of it’s properties.  It all ran on the UI thread in response to the user asking to print.  As a result, (if my analysis is correct) the initial data binding has not yet run, as we haven’t ceded control to the dispatcher.

Eventually, my UI control is asked to Measure and Arrange from my code on the UI thread, and finally UpdateLayout is called by my code.  Because I had set some of the properties in code (the Interval, and Interval Types) but not the Max and Min, when the control is being laid out it does all the work to create the stupid number of ticks for each hour in a year.

Then, inside UIElement.UpdateLayout() I have this call stack:

PresentationFramework.dll!MS.Internal.Data.DataBindEngine.Task.Run(bool lastChance) + 0x31 bytes   
PresentationFramework.dll!MS.Internal.Data.DataBindEngine.Run(object arg) + 0xb6 bytes   
PresentationFramework.dll!MS.Internal.Data.DataBindEngine.OnLayoutUpdated(object sender, System.EventArgs e) + 0x1e bytes   
PresentationCore.dll!System.Windows.ContextLayoutManager.fireLayoutUpdateEvent() + 0x154 bytes   
PresentationCore.dll!System.Windows.ContextLayoutManager.UpdateLayout() + 0x926 bytes   
PresentationCore.dll!System.Windows.UIElement.UpdateLayout() + 0x16 bytes   

So, it finally data-binds the rest of my values, and lays out the axis correctly with the intended values for Max and Min.

By switching everything to use data-binding instead mixing it with code-behind, the default values in the DateTimeControl work correctly (though the work is still wasted), and then the data-binding sets them to the new values.

It’s this wasted effort that concerns me, so future instalments will examine what can be done about it.

Wednesday, 25 July 2012

Landscape Printing and Preview in WPF: It’s the overload, stupid!

We’re just finishing up a new release for our weather station software, and thought it might be nice to add printing for our users.

The printouts lend themselves to Landscape orientation, but much as we searched, we couldn’t find a way to get the DocumentViewer control to show us the preview in Landscape mode.

In the end, there are a sequence of things that need to be done.

First, when you create your PrintDialog, make sure you set the PageOrientation property to PageOrientation.Landscape.  That in itself isn’t enough though.

So, now store the size of the paper from the successful print dialog, and construct your DocumentPaginator object.

var ms = printDialog.PrintTicket.PageMediaSize;
Size pageSize = (printDialog.PrintTicket.PageOrientation == PageOrientation.Portrait) ?
new Size(ms.Width.Value, ms.Height.Value) :
new Size(ms.Height.Value, ms.Width.Value);

var Paginator = new SamplePaginator(uc, pageSize);

Where uc some control or Framework Element.

And here’s the important bit.  When you return a DocumentPage, make sure you use the overload that takes the PageSize you calculated above.  If you do, then preview works great. 

I had naively assumed that overriding the PageSize abstract property would do this job, but it did not.

Sunday, 22 July 2012

Win8 WebView not ready for Prime Time

Yesterday (July 21, 2012) I spent the day at a Windows 8 DevCamp, and had a lot of support from Microsoft starting to get our Travel Advisories App ported to Windows 8.

The app renders html using the WebView control, but in trying to port it, I ran into a number of problems.

The first problem I encountered was that there is no equivalent of the OnNavigating event, raised by the WP7 control.  In WP7, what we did was perform a WebBrowserTask.Show() on containing whatever page was being navigated to.

Launching the web browser wasn’t that great of an experience on Win 8 in any case, so I decided I’d put that on hold.  That’s when the second problem appeared.

We construct the first page shown in the app in memory, and then use NavigateToString.  Unfortunately, the page doesn’t appear to accept clicks or navigate reliably when you first navigate.  Resizing the app on Win8 appears to make the WebView finally show it’s scroll bars, and take navigation hits.  On that note, I took the train back to Cambridge.

This morning I had a few more ideas.  I thought, that since I can’t pop open a new web browser on navigate, I’d try to handle the navigation to a new page in the app.  I do this by using the LoadCompleted event.

Unfortunately, this event isn’t reliably raised on a new page, nor is the NavigationFailed event.

My plan was to keep a count of the number of times navigation occurred (assuming that none of the pages raise back by themselves), and then allow navigation back by executing a javascript expression “window.history.back();” depending on how many pages I’d navigated.

Unfortunately, while this occasionally works, it also occasionally throws an exception.

So, all in all, I think the message in all this is: If your app needs an HTML viewer, your in for a world of pain.  I don’t know if this will be resolved before Win8 ships, as apparently it’s RTM in just 8 days.

Caveat Coder!

Saturday, 14 July 2012

WP7 ScheduledActionService = Scheduled Crash!

I’ve been on the trail of an irreproducible bug for a couple of weeks now, described on the forums

In effect, what has been happening is that ScheduledActionService.Find is throwing an exception, but I wasn’t able to duplicate the fault in our program Terminator which shows sunrise and sunset times.

However, we’ve been trying to get rid of some other navigation bugs, and it struck me that this was probably another banging on the back key bug.

And it turned out I was right, that’s exactly what it was.  If you repeatedly stop and start our app (and probably any other app that refreshes a live tile on startup) you can provoke this failure.

It appears the crux of the problem is that the previous instance of the app has not quite completed when you launch the new instance.  If you get the timing exactly right, the call to ScheduledActionService.Find will throw, and if you're even more lucky you’ll catch it in the debugger, and see that immediately after it fails, it throws a ThreadAbortException

That’s a pretty good clue that there’s some internal bug that is triggered by the previous instance not having let go.

My solution?  Initially I thought wrapping a mutex around the whole program would do it, but it still failed.  That implies that the abort on the previous version released the mutex, but hadn’t yet cleaned up the remainder of the ScheduledActionService.

So the working solution is to retry the call to ScheduledActionService.Find up to three times, delaying 100ms each time, to let the previous instance shut down correctly and get out of the way.  Timing of course may vary depending on your app.

Wednesday, 11 July 2012

Windows 8 CP acmFormatChoose Bug

Yesterday, I decided to take the plunge and install W8 CP on a VHD, as described by Ed Bott here

It took a good few tries, but the step I hadn’t counted on was when I created the VHD, it takes a very long time to complete, and the UI doesn’t show you much in the Disk Manager.

Eventually, the 64 bit version was all up and running, so I decided to try our program RIP Vinyl, and was pleasingly surprised that our custom built installer wrapper we use for all of our projects installed without a hitch.

So, I launched the program, and recorded a test stream from YouTube.  It worked straight away on Windows 8!

Unfortunately, when I went to change the recording format to MP3, I started to hit problems.  Pressing the format change buttons did nothing.

The code allows the user to change the recording format by selecting a format via the acmFormatChoose dialog, but unfortunately, no matter what I tried, I keep getting a return code of 0x0000000B, or MMSYSERR_INVALPARAM

I’ve now built a very simple test using VS2010 and an MFC dialog based app, which runs fine on everything but W8.

I haven’t yet tried on the 32 bit version of W8.

Here’s the code I run on a button press in my MFC app:

MMRESULT Cacmformatchoosetest10Dlg::CheckFormat()
int waveFormatExSizeMax;
TCHAR waveformatName[_MAX_PATH];
waveformatName[0] = 0;

acmMetrics(NULL, ACM_METRIC_MAX_SIZE_FORMAT, &waveFormatExSizeMax);

void *buffer = _alloca(2 * waveFormatExSizeMax);
WAVEFORMATEX &wfx = *( (WAVEFORMATEX *) buffer);

memset(&wfx, 0, sizeof(wfx));
wfx.cbSize = 0;
wfx.nChannels = 2;
wfx.wFormatTag = WAVE_FORMAT_PCM;

ZeroMemory(&acmfmtch, sizeof(acmfmtch));

acmfmtch.cbStruct = sizeof(acmfmtch);
acmfmtch.fdwStyle = 0;
acmfmtch.pfnHook = NULL;
acmfmtch.pszName = waveformatName;
acmfmtch.cchName = _MAX_PATH;
acmfmtch.pszTitle = _T("Title");
acmfmtch.hwndOwner = NULL;
acmfmtch.pwfx = &wfx;
acmfmtch.cbwfx = waveFormatExSizeMax;

acmfmtch.fdwEnum = ACM_FORMATENUMF_INPUT ;
acmfmtch.pwfxEnum = NULL;

return acmFormatChoose(&acmfmtch);

If anyone wants the entire project to try, please contact me via @WieserSoftware on twitter.

Friday, 6 July 2012

Yes, the Windows Phone Marketplace is still broken.

Some of you may have noticed me complaining on Twitter that my apps are not appearing in various marketplaces around the world.

I’ve been testing this by going to the marketplace, and searching for our publisher name (wieser) and then changing the country listed from the home country that appears to various others.

Oren Nachman (who did a great talk over here in London at a Dev Days a few years back) suggested I raise a ticket so I did.  Here’s the question:

I've noticed that some of my apps are still not visible in some of the marketplaces they should be a month after certification. What's gone wrong? Missing apps include some or all of: Skied Demon and Bridge Stenographer, Travel Advisories in the following marketplaces (at least) en-AU, en-NZ, en-SG de-DE, de-CH, de-AT, es-MX, es-ES Who knows how many more. I don't have the full list of marketplaces... Can you please advise?

To the Marketplace team’s credit, I got a response in less than a day this time, though the news isn’t good.

Thank you for contacting Windows Phone App Hub Developer Support. There is a known issue affecting some developers in markets outside of the US, where apps are not searchable by app title or keywords in some country's marketplaces, and only accessible via the deep link. Our engineers are aware of this top priority issue and are actively working to resolve it, as we understand that visibility is one of the most important aspects to app popularity. The ETR is currently next week provided by the engineering team.

We apologize for the inconvenience, and appreciate your patience while we resolve this matter, and work to improve the experience of the App Hub and the Windows Phone Marketplace.

The good news is, my apps will be discoverable, and I’ll finally be able to pay the rent from sales of my apps.  Or not.

In any case, now you know.

Wednesday, 4 July 2012

I really do hate hardware

Today one of my customers was trying to print with a Kodak ESP C310 printer and our program ( crashed when calling a WPF 4.0 program's Print Dialog.

We installed the latest drivers, but the problem remained.

As a last ditch effort I selected "print directly to printer" on the preferences for the printer, and it worked, so it appears there's some incompatibility with the Kodak print spooler.