Showing posts with label WPF. Show all posts
Showing posts with label WPF. Show all posts

Monday, March 17, 2008

Rob Relyea Hates my Mom

Mom, Dad, Kevin

My mother is a lovely woman. So I'm confused as to why Rob dislikes her so much.

Let me back up. Last August (when I was still at my former employer) I did some demos of new features in WPF 3.5 for Channel 9.

I was happy to see Rob reused some of the same demos for his MIX '08 Talk.

When I was watching the Add-In demo, I was surprise to see the data had remained the same--for the most part. My dad--Robert, my brothers--Brian and John, and I were still in the data set.

image

But my mother--Wynette--replaced by "Web Service". I mean, I know WCF is powerful, but it's a poor replacement for the most wonderful woman in the world--even if she has an uncommon name.

I did find it funny that the last name was left unchanged.

image

So come on Relyea. Let's hear it. I think I deserve an explanation. :-)

Friday, March 14, 2008

Reader Question: "Why WrapperElement<T> instead of Decorator?"

Yuval, via a comment, asks:

Can you explain why you chose to define WrapperElement<T> instead of simply inheriting from Decorator? They seem to be doing the same thing.

Ah, it is good to read my post "Don't subclass a Panel, unless you're making a Panel".

First, where do I used WrapperElement<T>? Well, at the moment, it's used in three places: FlipTile3D, Set, and Transition3D. In all cases it simply wraps a Viewport3D.

Now, could I simply use Decorator? Sure. I could subclass Decorator and do basically the same thing. Even easier: I could subclass Viewport3D.

Why don't I?

It's all about API usability and correctness.

When I write a control (especially for the bag-o-tricks) I try to make it "right".

If these controls subclassed Viewport3D, they should act like Viewport3D. Meaning, they should support someone added arbitrary lights, models, etc. They should support messing with and removing elements that are already added.

I don't want this.

Wrapping everything with a Decorator, just exchanges problems.

If I subclass Decorator, it would imply that I mean people to treat the control like a Decorator, which means accessing and changing the Child property.

I don't want this, either.

WrapperElement<T> does some very simple things.

  • Stores a Framework element as a child visual.
  • Adds it to its visual tree (via AddVisualChild)
  • overrides GetVisualChildCount
  • overrides VisualChildCount
  • overrides MeasureOverride
  • overrides ArrangeOverride
  • Provides protected access to the child so the subclass can play with it, but not the end user.

One should use WrapperElement<T> when one wants to use the implementation of an existing component--Viewport3D, Grid, etc.--but doesn't want the object model of that component exposed.

Make sense?

Thanks for the question, Yuval.

Happy hacking.

Saturday, March 8, 2008

Bag-o-Tricks March '08 Edition

January 12 was my last update to the bag-o-tricks. I couldn’t let two months go by without another refresh. There is a lot of stuff here I've already talked about. The code has been available via SVN for a while, but not in the convenient package.

The Goods

Binaries and Source [zip, 4.17MB]. Tested with Visual C# Express 2008.

The Details

  • VS Copy-to-HTML - New!
    • Blogged about it here.
  • NotifyWorker - New!
    • Blogged about it here.
  • HueConverter - New!
    • Makes it easy to create a pretty spectrum of colors for a number of items
  • Animating Tile Panel
    • Renamed AnimateNewItem property to AnimatesNewItem
    • Made initial load not animate new items
    • Polished the demo code
  • CompositionTargetRenderingListener
    • Added Dispose and used it where appropriate
    • Added WireParentLoadedUnloaded helper method
  • DemoCollection
    • Added Reset command
    • Moved to library assembly
  • Extentions
  • FlipTile3D
    • Moved to WrapperElement<Viewport3D>
  • FolderPicker
    • Moved to new SelectRecursive implementation
  • WPFUtil
    • Added HSB-RGB converter
    • Created ‘Animate’ methods to abstract physics model. Moved FileTile3D, AnimatingTilePanel, and ZapDecorator to these.
  • ZapScroller
    • A lot cleaner. Removed a completely unnecessary layer of complexity.
    • Added the ability to template the buttons. Thanks for the suggestion, Atul.
  • WPF Set
  • General
    • A bunch of renaming of namespaces. Most of the new stuff will exist under J832.Wpf

As always, the SVN location has been updated. Patches are always welcome. (I've had one taker on this. Very cool to do an apply patch. Would love to do it again.)

Enjoy and happy hacking!

Tuesday, February 5, 2008

Xaml Serialization with WCF

So I'm reading RESTful Web Services and I got inspired. Sounds like a great way to expose data on the web.

I've heard great things about WCF support for REST, with their new WebGet attribute and such.

So I fire up Visual C# Express and get hacking.

Amazing what one can do in 54 lines of code.

using System;
using System.ServiceModel;
using System.ServiceModel.Web;
 
namespace J832
{
class Program
{
static void Main(string[] args)
{
Time time = new Time();
 
using (WebServiceHost host = new WebServiceHost(time, new Uri("http://localhost:8080/")))
{
host.AddServiceEndpoint(typeof(ITime), new WebHttpBinding(), "time");
host.Open();
 
Console.WriteLine("enter to close...");
Console.ReadLine();
}
}
}
 
[ServiceContract]
public interface ITime
{
[OperationContract, WebGet]
DateTimeCount GetTime();
}
 
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Time : ITime
{
public DateTimeCount GetTime()
{
return new DateTimeCount(DateTime.Now, m_count++);
}
 
private int m_count;
}
 
[Serializable]
public class DateTimeCount
{
public DateTimeCount(DateTime now, int count)
{
Now = now;
Count = count;
}
 
public DateTime Now { get; set; }
public int Count { get; set; }
}
}
 

And we open a browser to http://localhost:8080/time/GetTime:

<DateTimeCount xmlns="http://schemas.datacontract.org/2004/07/J832" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<_x003C_Count_x003E_k__BackingField>0</_x003C_Count_x003E_k__BackingField>
<_x003C_Now_x003E_k__BackingField>2008-02-04T23:28:21.987561-08:00</_x003C_Now_x003E_k__BackingField>
</DateTimeCount>

Isn't that *cough* *choke* pretty *gag*.

I suddenly had a flash of nostalgia...of longing...for Xaml.

Xaml: that wonderful language where document-object mapping is implicit, trivial, and human-understandable.

(I'd love to link to Our 7 Goals of Xaml introduction but the link is dead. Help, Rob?)

Thankfully, XamlWriter is publicly available and WCF is insanely, gratuitously, wonderfully extensible.

  • WCF services have endpoints.
  • Endpoints have contracts.
  • Contracts have operations.
  • Operations have behaviors.
  • Behaviors have formatters.
  • Formatters can have custom messages--for instance, XamlMessage.

Thanks to Maheshwar Jayaraman for the post (and sample) on his old blog that got me started.

The code is a bit longer, but it's mostly boiler plate:

using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Web;
using System.Windows.Markup;
using System.Xml;
 
[assembly: XmlnsDefinition("http://schemas.j832.com/demo/2008", "J832")]
 
namespace J832
{
class Program
{
static void Main(string[] args)
{
Time time = new Time();
 
using (WebServiceHost host = new WebServiceHost(time, new Uri("http://localhost:8080/")))
{
ServiceEndpoint endPoint = host.AddServiceEndpoint(typeof(ITime), new WebHttpBinding(), "time");
foreach (OperationDescription operationDescription in endPoint.Contract.Operations)
{
operationDescription.Behaviors.Add(new MyOperationBehavior());
}
 
host.Open();
 
Console.WriteLine("enter to close...");
Console.ReadLine();
}
}
}
 
[ServiceContract]
public interface ITime
{
[OperationContract, WebGet]
DateTimeCount GetTime();
}
 
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class Time : ITime
{
public DateTimeCount GetTime()
{
return new DateTimeCount(DateTime.Now, m_count++);
}
 
private int m_count;
}
 
[Serializable]
public class DateTimeCount
{
public DateTimeCount(DateTime now, int count)
{
Now = now;
Count = count;
}
 
public DateTime Now { get; set; }
public int Count { get; set; }
}
 
public class MyOperationBehavior : IOperationBehavior
{
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
 
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }
 
public void ApplyDispatchBehavior(
OperationDescription operationDescription,
DispatchOperation dispatchOperation)
{
dispatchOperation.Formatter = new MyMessageFormatter();
}
 
public void Validate(OperationDescription operationDescription) { }
}
 
public class MyMessageFormatter : IDispatchMessageFormatter
{
public void DeserializeRequest(Message message, object[] parameters) { }
 
public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
{
return new XamlMessage(result);
}
}
 
public class XamlMessage : Message
{
public XamlMessage(object value){ Value = value; }
 
public override MessageHeaders Headers
{
get { return new MessageHeaders(MessageVersion.None); }
}
 
protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
{
XamlWriter.Save(Value, writer);
}
 
public override MessageProperties Properties{ get{ return new MessageProperties(); } }
 
public override MessageVersion Version{ get{ return MessageVersion.None; }}
 
public object Value { get; private set; }
}
}

And (drumroll) the output from http://localhost:8080/time/GetTime:

<DateTimeCount Now="2008-02-04T23:53:18.884273-08:00" Count="14" xmlns="http://schemas.j832.com/demo/2008"/>

Isn't that pretty? The XmlnsDefinition assembly attribute is the icing that gives you the pretty xmlns.

Do other folks love Xaml? Do you wish it was available outside PresentationFramework.dll?

Let me know. I'm thinking of starting a campaign.

Hear that, Rob? :-)


kick it on DotNetKicks.com

Wednesday, January 30, 2008

WPF Set: Now in 3D!

I last discussed Set about 3 weeks ago. At the time, my implementation was based on buttons. It worked fine, but when I saw how it looked on a friend's machine running the 'classic' theme I was appalled.

I got thinking: there has to be a way to make this game more interesting.

The answer: 3D and gratuitous animation.

The address for the XBAP hasn't changed: http://j832.com/workBlogFiles/WpfSet/

WPF Set - 3D!

The source code has been checked-in to my SVN repository. The code files actually live under J832.Wpf.BagOTricksApp, but you can build and play with the stand-alone app by loading the J832.WpfSet solution file. (Thank you, CSProj file linking feature.)

Cool stuff:

  • The listbox on the right that displays the matched sets now uses AnimatingTilePanel.
  • First use of the new WPFUtil class, which exposes a set of Animate methods. These basically formalize and abstract my simple attraction-based physics model. I support 1-dimensional (double), 2-dimensional (Point/Vector), and 3-dimensional (Point3D/Vector3D) animation. Check out ZapScroller and AnimatingTilePanel in the source to see the other places these are used.
  • I've also added some helper extension methods to WPFUtil. Very useful.
  • Implemented a couple of super-simple 3D components: ButtonBase3D and ToggleButton3D. An interesting exercise. Hoping to move FlipTile3D to these in the future.

Not cool stuff:

  • Keyboard accessibility has evaporated. With the previous implementation, you could tab around to the buttons and press spacebar to select/unselect a "card". More work than I signed up for in this iteration, but something to keep in mind.
  • WPF handles z-order great in 3D unless you have transparency in your textures. Take a look at the corners of raised cards. Very frustrating. The only work-around is to manually re-order the child elements. Lame.
  • Perf is great...on my super fast workstation. Even on my relatively beefy laptop, I notice some glitching when things are moving around. Ideally, I'd detect machines hardware limitations and degrade the animations. I should probably offer a "turn off the fancy stuff" check box. Eh, V3.

This will of course be included in the next drop of the full bag-o-tricks, but I thought it was cool enough to share out-of-band.

Have fun.

Sunday, January 27, 2008

Improving NotifyWorker (and why blogging makes me smarter)

My current day job consists of writing middleware and web-based LOB software for an actuarial services company.

David is my primary collaborator and a fellow blogger. We get along amazingly well, but every once in a while, we disagree. The amazing thing, though, is how we handle disagreements.

I'm at a point where I look forward to disagreement! For a reason that I hope more people can come to realize.

If we disagree about how something should work, it's almost always an opportunity for improvement. Specifically, at least one of the following is likely to happen:

  • I get smarter
  • He gets smarter
  • The code gets better

Quite often, it's all three. I know this sounds like motivational speaker talk or corporate training fodder, but it's true. The fact that we often discuss our disagreements with explicit reference to masters is also helpful.

I blog for similar reasons. I'm far from a CLR, C# or WPF expert, at least relative to the experts I've had the opportunity to work with.

I'm just a geek trying to figure things out and sharing as I go. Hopefully what I share is helpful. Often, though, blogging has self-serving benefits. Just taking the time to write down explanations for what I'm doing forces me to rationalize edge cases.

If I had never blogged the recursive flattening craziness, I would have never have seen all of the edge cases I had missed the first time out.

Anyway, to my point.

Simon was kind enough to point out a deadlock in my NotifyWorker code.

FYI: At this point, looking at the code will be quite helpful.

I was doing a Thread.Join() in the middle of a lock. Of course, if the thread in question is waiting on the lock which is held, the thread will never exit. The result: the Join() call will never return.

Deadlock, indeed.

Props to Simon for pointing out this problem.

I moved the Join() outside of the lock and thought I'd fixed the problem.

Maybe, but I also realized another problem.

  • Dispose is likely called from the UI thread.
  • Dispose calls Join on my worker thread as discussed above.
  • The worker thread does Dispatcher.Invoke twice.

What happens if Dispose is called between my check for m_disposed and when the Invoke actually gets to the UI thread?

Dispose will be tying up the UI thread waiting for Join() to return. The worker thread will never terminate because it's waiting for Invoke to get to the UI thread.

Deadlock again.

Darn.

The solution: Move from Dispatcher.Invoke to Dispatcher.BeginInvoke.

BeginInvoke returns an instance of DispatcherOperation. This class has an Abort method which can be used to abort a pending BeginInvoke.

I now have an instance field containing the pending operation, if one exists, which can be aborted via Dispose before the call to Join. I also now enforce that Dispose must be called on the UI thread (by adding a call to VerifyAccess). This ensures that Abort is not called while the worker is synchronized to the UI thread.

After some careful uses of DisaptcherOperation.Wait and some paranoid null checks, I think I'm okay. 

As an aside, I also noticed that Dispose() will throw in the case where NotifyNewWork() has never been called. I fixed that, too.

*Sigh*

Threading code is tough. I had to stand up and pace around a few times as I mentally worked through possible edge cases.

As I've said before, I think this implementation is better, but it would be a stretch for me to claim it's correct. Hopefully, it's more correct.

Please keep the questions, comments, and feedback coming (after you take a look at the post about contacting me).

The code will get better and we'll all get smarter.

 

Happy hacking.

Tuesday, January 22, 2008

Responsive UI with Unresponsive Data: NotifyWorker

Yes, I finally got off my SelectRecursive kick. This is penance.

The 3-box Visio Diagram

How many user interfaces do you design that follow this pattern?

image

Okay, almost all of them.

A frequent refrain from users (well, at least me):

Regardless of what you're doing behind-the-scenes, the interface should never hang, stutter, or lag. Ever.

Easily said, right?

Well, while hacking on one of my side projects, I ran into this problem.

I put on my "framework guy" hat and asked, "Can I formalize this into a pattern?"

My answer: NotifyWorker.

Now before I get into NotifyWorker, I should mention BackgroundWorker: a frequently used and well thought-out component supplied by our friends in the Baseclass Library (BCL) for this kind of thing.

The trick performed by BackgroundWorker is simple, but important: allow start/stop/cancel of a task on another thread. It also keeps developers sane who have to deal with strict rules about what can only be done on the UI thread.

The type of tasks for which NotifyWorker was designed fit into a special niche that I think BackgroundWorkre misses:

  • Tasks that are long enough to lag the interface (0.1+ seconds) but are too short to require tracking of percent complete (so shorter than, say, 5 seconds).
  • Tasks that are repeated constantly as the UI updates. Think of recalculating a math/physics model or checking a server-hosted word list for a type-a-head find control.
  • Tasks where the work to be done is--in relative terms--longer than the work to update the user interface. If the task takes 0.1 seconds, but updating the 1,000 controls in your window takes 2 seconds, you should start by tweaking your architecture before using NotifyWorker.

How does it work?

(Reflector is how I learn all of my new APIs.)

image

The constructor takes three parameters that outline the diagram above and the workings of NotifyWorker:

  • Func<bool> prework: this is a delegate that is called on the Dispatcher (UI) thread in a WPF application. Delegate passing is a trick I use a lot, and you should, too. The method does the work to take interesting parameters from the UI (control values, etc.) and puts them in a spot where NotifyWorker can safely play on a non-UI thread. If you decide there is nothing interesting to do, you can return false, telling NotifyWorker "never mind".
  • Action work: this is where the magic happens. This function is called on a background thread which will leave your UI nice and responsive. Be super careful that 'work' only touches state (fields, etc) that you've safely set aside during prework.
  • Action postWork: the last box in our diagram. It takes the work done in 'work' and updates the UI accordingly. This method, as well as 'preWork', synchronizes the UI and background threads, so you don't have to worry about locking.

How do you kick things off? Call NotifyNewWork. This method can be called often, if you want. Put it in the 'Changed' handler of your favorite Slider or TextBox. If NotifyWorker is idle, it will wake up and immediately call preWork. If NotifyWorker is busy, it'll loop right around to get new input values as soon as postWork is done.

A demo

image

Sexy, huh?

The scenario is simplistic and contrived, like so many good demos.

Both sets of UI do the same thing: take the values from two sliders and sum them. Not too complicated.

For some strange reason, the sum 'routine' takes 0.25 seconds to return a result.

Maybe it's that new math...

Eh, it's probably the Thread.Sleep(250) I put in the method.

Anyway, use your imagination. You'll notice that the 'slow' UI is, well, slower. It lags a bunch as you drag the sliders since it's calling the expensive sum method on the UI thread.

The NotifyWorker controls do much better because the expensive call is hidden off-thread.

Simple, but useful.

Odds and ends

You'll notice a LastClientExceptionEventArgs property and a ClientException event. These are designed to make it a bit easier to handle non-critical exceptions during 'work'. The demo code actually shows this off pretty well.

You'll also notice that NotifyWorker implements IDisposable. Dispose will make sure to end any background work when you're done with NotifyWorker.

Under the covers, NotifyWorker uses LockHelper and some of the pulse/monitor tricks I wrote about a few months ago.

That's about it.

Since NotifyWorker is hard-wired to the notion the Dispatcher, it lives in J832.Wpf.BackOTricksLib. The demo is in the Bag-o-Tricks app under NotifyWorker.

I'm not going to do an official release (zip with binaries/source) of the bag for this sample. It's cool, but not cool enough to warrant a whole new drop.

It also gives you an excuse to play with SVN, if you haven't already. You can always just manually download the code from my SVN share using your browser, too.

Let me know if this works well for you.

 

Happy multi-threaded hacking!

 

Note: I've had some funky auth issues with Firefox on the SVN server lately. Let me know if you have any trouble.

Friday, January 18, 2008

Source code, HTML, and an Exercise for the Reader

No clue why I want to be vague about this. Probably because I'm late for a party, but really wanted to share.

And you're smart. You'll figure it out.

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="VS Copy-Paste to HTML" >
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <RichTextBox Name="m_richTextBox" TextChanged="m_richTextBox_TextChanged" />
        <TextBox Grid.Column="1" Name="m_textBoxHTML" />
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Web;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void m_richTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        m_textBoxHTML.Clear();
        m_textBoxHTML.AppendText("<div style=\"font-family:monospace;\">" + Environment.NewLine);
        foreach (Paragraph paragraph in m_richTextBox.Document.Blocks.Cast<Paragraph>())
        {
            foreach (Run run in Decompose(paragraph.Inlines))
            {
                m_textBoxHTML.AppendText(toCleanedColoredSpan(run));
            }
            m_textBoxHTML.AppendText("<br/>" + Environment.NewLine);
        }
        m_textBoxHTML.AppendText("</div>");
    }

    private string toCleanedColoredSpan(Run run)
    {
        SolidColorBrush solidColorBrush = run.Foreground as SolidColorBrush;
        if (solidColorBrush == null)
        {
            return cleanString(run.Text);
        }
        else
        {
            if (solidColorBrush.Color == Colors.Black)
            {
                return cleanString(run.Text);
            }
            else
            {
                string colorString = solidColorBrush.Color.ToString();
                Debug.Assert(colorString.Length == 9);

                colorString = colorString.Substring(3);

                return string.Format("<span style=\"color:#{0};\">{1}</span>",
                    colorString, cleanString(run.Text));
            }
        }
    }

    private string cleanString(string source)
    {
        source = HttpUtility.HtmlEncode(source);
        source = source.Replace(" ""&nbsp;");
        return source;
    }

    private static IEnumerable<Run> Decompose(IEnumerable<Inline> inlines)
    {
        foreach (Inline inline in inlines)
        {
            if (inline is Span)
            {
                foreach (Run run in Decompose(((Span)inline).Inlines))
                {
                    yield return run;
                }
            }
            else if (inline is Run)
            {
                yield return (Run)inline;
            }
            else
            {
                throw new NotSupportedException();
            }
        }
    }
}

Saturday, January 12, 2008

A REAL update to the Bag-o-Tricks

The last update to the bag-o-tricks was pretty lame, I'll admit.

As I discussed a bit ago, a lot of people code to "get it done yesterday". It's clear, looking through a bunch of the code, that I had this mentality about a lot of the samples. Moreover, it's clear that the masters of code quality, maintainability, and best-practices were often ignored.

Here's an attempt to put my money where my mouth is.

The Goods

Binaries and Source [zip, 4.3MB]. Developed with Visual C# Express 2008.

The Details

  • Set - New!
    • The XBap I posted last week is now part of the bag-o-tricks.
    • Did some clever project linking to allow building XBap without duplicating files.
  • Interactive 3D - New!
    • Added the 3D demos I discussed a while ago
    • A lot of clean-up to make the code more suitable for general use
  • AnimatingTilePanel
    • Ability to animate in newly added items
    • Made dampening make sense: bigger number = more dampening
    • Add Variation property to make item movement make more organic, if desired
    • Added validation to the public properties
    • Moved frame tick register/de-register to new CompositionTargetRenderingListener
    • Perf: Stopped ‘ticking’ when nothing is changing
    • Stopped using clock time between ticks to affect animation: complexity with little benefit
    • Made ItemHeight/Width attached properties that can be added to a host ItemsControl.
    • Demo: moved to DemoCollection -> now you can see how changes to the source collection are reflected
  • Graph
    • Removed a completely silly use a Dictionary. Clever perf optimization that made the code bloated, confusing, and probably slower.
    • Renamed CoefficientOfDampening -> Dampening
    • Rename FrameRate -> Attraction
    • Removed unneeded HideAnimationManager -> thank you anonymous methods.
    • Demo: A bunch of clean-up in the Node data files. Should never have used DispaterObject.
    • Demo: Moved to dispatcher timer for churning the graph nodes: a lot cleaner and more efficient than a separate Thread.
  • Zap
    • Removed shameful implementation of IPropertyChanged on top of a DependencyObject
    • Eliminated gratuitous subclassing of DependencyObject by ZapCommandItem
    • Removed FirstPreviousNextLastCommand (and associated enum) -> thank you ActionICommand
    • Fixed some pretty glaring bugs in not-so-edge cases
    • Demo: moved to DemoCollection
  • ActionICommand - New!
    • Commands often expose the functionality of existing methods. So why can’t exposing a command be as simple as wrapping the existing method? Exactly.
  • WrapperElement - New!
    • A while ago, I discussed not subclassing Panel unless you’re making a Panel. Well, sometimes all you want to do is compose a single element without exposing it's state to the world. WrapperElement makes the process of wrapping a child element trivial.
  • CompositionTargetRenderingListener - New!
    • A class that can be used to manage listening to the static CompositionTarget.Rendering event. I use this pattern so much, it made sense to make a separate class.
  • Demo: DemoCollection - New!
    • Writing controls that handle INotifyCollectionChange can be tough. Wouldn’t it be nice to have a class that makes testing (and then showing off) your list-bound control easy? I thought so.
  • General
    • Removed a lot of unused parameters and privates. Thanks, FxCop.
    • Demo: Moved the introduction page to FlowDocument. Why I didn’t do this a year ago, I’ll never know.

As always, the SVN location has been updated. Patches are always welcome (No one has taken me up on this yet. Would love to try it out.)

Enjoy and happy hacking!

Tuesday, January 8, 2008

WPF 3.5 Demo Posted: Add-ins and Interactive 3D

Way back in August, I posted about my last Channel 9 interview where I discussed new stuff in WPF for 3.5. At the time, I was a slacker about getting the code for the demos out.

WPF 3.5 Demo Code

Well Karsten has picked up my slack.

I only have C# 2008 Express installed and for some reason it doesn't like how complicated I made the solution, so I can't claim to have built/run the code as Karsten has posted it. I'm sure it's fine, though.

WPF Set - This is a good stopping spot

WPF Set - 2008-01-08

  • The graphics pretty much match the actual game now.
  • Moved the New Game button to minimize accidental clicking.
  • Some small tweaks to the game logic.

URLs are the same as before:

Bleh! The third night this darn game has kept me up past midnight. I need another hobby.

Sunday, January 6, 2008

The Game "Set" in WPF (Redux)

Yesterday (well, early this morning) I posted my first stab at the game Set implemented in WPF.

Amazing what 20 hours of bake time can do.

  • Removed insanely complex and unnecessary optimization that made the code for SetGame.TryPlay almost un-readable.
  • Cleaned up the 'test' mode by removing it. If you build/run DEBUG, you'll get a Test button. If you build/run RELEASE, you won't.
  • Used binding in the WPF app where it makes sense.
  • Added a bunch of caching to the card generation on the WPF side. Re-creating a bunch of brushes and pens is silly.
  • Added a test project: WpfSet_Test to run the data model through its paces.
  • Fixed deployment to actually use Visual Studio Deploy functionality. (Yesterday, I just uploaded the files from the build directory.) If you accessed the XBAP yesterday, please refresh and make sure the title says at least "WPF Set - 2008-01-06" and not "WPF Set - 2008-01-05." If refreshing doesn't move you beyond 2008-01-05, there is something fishy with your application cache. Drop me a line and I'll try to fix it.

The XBAP URL hasn't changed.

I've combined the source and binaries into one zip file [53KB]. I've always thought having dated copies of files was goodness, but then I realized if someone links to the old version, they'll never get updates. I'm going to try to keep an updated read-me in the zip file so folks know what version they've downloaded.

As always, send me an email or a comment if you have questions.

Happy Setting.

The Game "Set" in WPF

Update - 2008-01-06: See this post for new details. Links below have been changed.

In theory, I have so many more important projects to work on. This was one of those ideas that got legs faster than I realized I should be working on something else.

If you haven't heard of Set, check it out company site or the Wikipedia entry.

WPF Set - 2008-01-08

Building the data model was half the fun. This is some of my first code that uses Linq features from .NET 3.5. I'm blown away. Without a doubt as big as generics. Wow!

The UI is super minimal and that was the goal. Get it all working and ponder prettier/sexier things later.

So, if you want to play:

As I've discussed before, you can also take a look at the source on my SVN share using you favorite SVN client.

Future ideas:

  • Add a timer and a n