20

01/12

WP7 Navigation in MVVM

11:08 pm by admin. Filed under: MVVM,WP7

It’s ok.  It happens to all of us.  One second, you’re just standing there, with C# flying from your fingers like magic sparks, when—BAM– it hits you.  You need to navigate to another page.  In codebehind, it’s easy, right?  You just hit up your navigation service and tell it to navigate, and it just happens for you.  But what if you’re using MVVM?

One of the approaches I’ve seen is to pass a navigation service into your view model.  I’m not a real fan of this approach, for a couple reasons.  First off, unless you use an interface and dependency injection, you’re losing quite a bit of testability in your view model.  Also, even if you do, suddenly, your view model needs to know an awful lot about your application, which is something that you want to SOLID-ly avoid.

Jesse Liberty suggests getting access to the navigation service by reaching back into your app to get access to it, like this:

 

var rootFrame = (App.Current as App).RootFrame;
rootFrame.Navigate(new System.Uri("/Views/Page2.xaml", System.UriKind.Relative));

For something quick and dirty, that’s probably fine, but any unit testability is completely gone at that point, as your view model now has a hard dependency on the WP7 application object.

From my standpoint, neither of these options are optimal.  My preference is to use messaging to let a global navigation service take over.  That way, we can maintain testability, and our view models can be blissfully ignorant of the complexities of navigation.  The way I do this is using GalaSoft’s MVVM Light toolkit for Windows Phone.

The first thing you need to do it this way is a global object to handle your navigation requests.  I call mine (somewhat unoriginally) “Navigator”:

 

    public class Navigator
    {

        private PhoneApplicatoinFrame RootFrame;

        public Navigator(PhoneApplicationFrame frame)
        {
            RootFrame = frame;
            RegisterMessages();
        }

        private void RegisterMessages()
        {
            Messenger.Default.Register<ShowTrackerMessage>(this, ShowTracker);
        }

        private void ShowTracker(ShowTrackerMessage msg)
        {
            RootFrame.Navigate(new Uri("/Views/ItemLocationCompassView.xaml", UriKind.RelativeOrAbsolute));
        }

}

I need this Navigator object to have the same lifetime as my application, so I need to declare it in my App.xaml.cs file:

 

        private static Navigator _navigator;
        public static Navigator Nav
        {
            get { return _navigator; }
        }

And we need to instantiate it in the constructor of the App, and we need to pass in the application’s RootFrame.  I instantiate mine just after the if block that checks if the Debugger is attached:

 

    _navigator = new Navigator(this.RootFrame);

 

Now, in my view model, when I need to navigate, I can just send a message:

        private void ShowTracker()
        {
            if (SelectedItem != null)
            {
                Messenger.Default.Send(new ShowTrackerMessage());
            }
        }

 

After that, whenever I need to add new navigation, it’s just a matter of defining a new message, making a quick change to my Navigator to listen for that message, and the appropriate code in my view model to make the call.  Easy-peasy.

I’ll leave you with a couple of closing thoughts.

Messages: You have two choices here.  You could use a generic message with an enum property to determine the page to which to navigate, or you can have a specific message for each navigation event.  I personally choose the individual message route, but it’s probably an equal amount of effort.

Love your code, but never be in love with your code: When I first implemented this, I had a really convoluted way of setting the navigation capabilities in my Navigator class using the main page’s OnNavigatedTo method.  After I wrote this article and thought about what Jesse was doing, I found that I was able to use his RootFrame approach and just set everything up in the application’s constructor.  It wasn’t until I was completely finished with this article that I figured that approach was better than mine, so I refactored the code, then refactored the article.

I’m still (always) learning: I hope this helped you out, but if you think you have a better way of handling this type of navigation, let me know.  I’d love to hear it and discuss the merits of any additional approaches.

28

02/11

MVVM ComboBox Fun

9:59 pm by admin. Filed under: MVVM,WPF,XAML

Imagine that you have a combobox on a window.  When the selected item changes, you want to verify that the selection is allowed to change, and, if not, revert it back to the original value.

Using an MVVM approach (in this case with MVVM Light as support), it seems like a relatively easy thing to do.   All that is necessary is to handle the PropertyChanged event, and set the value of the SelectedItem back to the previous value.

Maybe something like this:

XAML:

<Window x:Class="TestDropdownApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <ComboBox Width="120"
                  Height="24"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Top"
                  ItemsSource="{Binding Names}"
                  DisplayMemberPath="Name"
                  SelectedItem="{Binding SelectedName, Mode=TwoWay}" />
    </Grid>
</Window>

 

ViewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using GalaSoft.MvvmLight;
using System.ComponentModel;

namespace TestDropdownApp
{
    public class ViewModel : ViewModelBase
    {
        public ObservableCollection<MyUsers> Names { get; set; }

        private MyUsers _selectedName;
        public MyUsers SelectedName
        {
            get { return _selectedName; }
            set
            {
                if (_selectedName != value)
                {
                    _selectedName = value;
                    RaisePropertyChanged("SelectedName");

                }
            }
        }

        public ViewModel()
        {

            Names = new ObservableCollection<MyUsers>();

            Names.Add(new MyUsers { Name = "Oleg" });
            Names.Add(new MyUsers { Name = "Rob" });
            Names.Add(new MyUsers { Name = "Craig" });

            this.PropertyChanged += ((src, evtArgs) =>
                {
                    if (evtArgs.PropertyName == "SelectedName")
                    {
                        if (SelectedName.Name == "Rob")
                        {
                            SelectedName = Names.First(n => n.Name == "Oleg");
                        }
                    }

                });
        }

    }

    public class MyUsers
    {
        public string Name { get; set; }
    }
}

 

On the surface, what this looks like it should do is check to see if the user is trying to select “Rob” from the list.  IF “Rob” is selected, the list box should select “Oleg” instead.

So, why would you ever want to do this?  Well, the situation we have is that a change to the ComboBox actually triggers a message that asks the user “Is it ok to change context”?  The user has the ability to cancel this context change, and, if they do, we want to set the ComboBox back to the original value, not the changed value.

The problem is, we’re trying to do all this on the same thread that is processing the SelectionChanged event.  That means even though we’ve reset the SelectedName to the original item, the UI never processes this.

We noodled on this for a while before we came up with a solution.  What we need to do is let the UI finish processing, then change the selection.  We do that by implementing a BackgroundWorker to send our message.

 

Revised Code:

public ViewModel()
{

    Names = new ObservableCollection<MyUsers>();

    Names.Add(new MyUsers { Name = "Oleg" });
    Names.Add(new MyUsers { Name = "Rob" });
    Names.Add(new MyUsers { Name = "Craig" });

    this.PropertyChanged += ((propSource, propEventArgs) =>
        {
            if (propEventArgs.PropertyName == "SelectedName")
            {
                if (SelectedName.Name == "Rob")
                {
                    BackgroundWorker bw = new BackgroundWorker();

                    bw.DoWork += ((doWorkSource, doWorkEventArgs) =>
                        {
                            doWorkEventArgs.Result = doWorkEventArgs.Argument;
                        });

                    bw.RunWorkerCompleted += ((runWorkCompletedSource, runWorkCompletedEventArgs) =>
                    {
                        SelectedName = Names.FirstOrDefault(x => x.Name == (string)runWorkCompletedEventArgs.Result);
                    });

                    bw.RunWorkerAsync("Oleg");
                }
            }
        });
}

In this revision, we set up a quick DoWork and RunWorkerCompleted.  We pass the “old” value in as an argument to the DoWork handler, and, within DoWork, all we do is hand off that argument to the “result” of RunWorkerCompleted.

Within RunWorkerCompleted, we can set the SelectedName to the value of the result, and the selection is changed correctly.

It’s a little hokey to do, but it keeps us out of the codebehind, and it works consistently.

27

12/10

Gigs Gone Wild

7:21 am by admin. Filed under: Hardware

A few days ago, I lucked out on a deal for an Intel Core i7-870 processor, so I picked it up, along with 8 gigs of memory and a new Asus motherboard.  I had wanted, for a while, to update my Intel quad-core system to something more modern, and, at the price I found, I couldn?t pass it up.

My old PC had something of a mishmash of drives.  I had a 1.0TB Seagate running at 7200RPM, which was my main drive.  I had a Samsung ?green? (slow 5400 RPM) terabyte drive that I used for system backups, and I had two tiny 160GB drives set up in BIOS as a striped RAID array.

I assumed that the RAID array would not transfer from one motherboard to another, so I backed up the 20 gig that I needed from the array, and figured I would just start it from scratch on the new motherboard.

After a bunch of typical new-motherboard issues (driver installation and the lot), I finally got my system up and running.  However, I had two really weird (but somewhat related) problems.  First of all, disk access seemed really slow?in fact I had a system crash while trying to copy several gigabytes from one volume to another.  Also, when I tried to go into disk management, it just hung on ?Connecting to virtual disk service??  Weird.

I searched around to see if I could find anything online, but wasn?t able to spot anything that looked exactly like my problem.  A couple of discussions seemed to point to hardware issues on the drives.  The cure, these articles said, was to unplug all but your OS drive, reboot and try again.  If that worked, then one of the unplugged drives was your issue.

Now, having been elbow deep in the guts of my tower all day, I had no desire to crack the case once again.  So I decided to take a look at what ?diskpart? had to say about my drives.

You can start diskpart from a cmd window.  It?s a powerful, and potentially dangerous, tool.

image This screenshot shows how things look now?after the problem was fixed.  Before this, Disk 2 showed as completely full, and Disk 3 showed as completely empty.

I surmised this might be the root of my problem, so I used diskpart to set both of those disks to ?offline.?

To do this, I ran the following diskpart commands:

SELECT DISK 2
OFFLINE DISK
SELECT DISK 3
OFFLINE DISK

 

Once I did that, I tried connecting to the disk manager again, and it worked.  I then used the disk manager to delete both volumes, and to create a Windows 7 based spanned volume.

Since then, the computer has become significantly more responsive and stable.

18

12/10

Crowd-sourcing Airport Security

5:25 am by admin. Filed under: Uncategorized

I just had the oddest thought occur to me.  Is it time to crowd-source airport security?

I was reading an article about a man who inadvertently took a loaded pistol through the airport security line and didn?t discover it until he reached his destination.  This, coupled with the news that nearly 70% of the items that testers try to get through airport security makes me think that someone is asleep at the imageswitch (or at least the screen).

It?s easy to imagine that someone could get bored looking at the x-rayed insides of thousands of bags per day.  You ever drive all the way to work and not been able to remember most of the trip?  I know I have.   It’s the same thing–routine becomes–well, routine.  You look for the anomalies, and, apparently, most of the time they get missed.

So, what if we tapped into the unbridled power of the Internet to do our screening?  In addition to the blue-suited TSA agent, send the x-ray image off to a website where anonymous reviewers can see the image and look for anything suspicious.  If they see something, they can click on the screen or draw a box around the item.

If some magic number of people–Maybe 20.  Maybe a hundred–agree that there’s nothing suspicious, no red flags are raised.  If less than that magic number participate, or any of them mark an item as suspicious, the agent is alerted, and can review the image in more detail, or do secondary screening on the item.

Couple this with image-processing software that can augment the human screeners, and you might have a winner.

And if the idea of anonymous crowd-sourced review scares you a bit, what about  having a bunch of back-up TSA screeners in a remote location to help supplement the front-line agent?

Great idea?  Or Greatest idea?

Picture source: flickr user steuben.  Licensed under creative commons.

06

07/10

I Love LINQ

3:27 am by admin. Filed under: Uncategorized

For quite a while, the organization where I work has been stuck on Visual Studio 2005 and C# 2.0.  We did it for a lot of valid reasons, including the not-insubstantial upgrade cost for close to a hundred developers.  When you talk about slapping down half a mil just to keep your developers up to date, management tends to shy away.

Through a number of happy circumstances, that changed this year, and we?ve just finished upgrading our developers to Visual Studio 2010 Ultimate.  Along with that came an upgrade to the .NET framework 3.5, and a pending upgrade to 4.0, once we clear some server team hurdles.

Of all the features I?ve been able to take advantage of in C# 3.5, the one that I find most useful is LINQ.  Whether LINQ to SQL, LINQ to XML, LINQ to TFS (yeah, really), or even just plain ol? LINQ (to collections), I find myself using LINQ in almost every aspect of development.

Now comes the fun part.  Moving a somewhat-static (not quite stagnant) development organization to take advantage of these features.  Suggestions?

29

05/10

What do Panera and Amazon?s Kindle Have in Common?

7:10 pm by admin. Filed under: Uncategorized

I recently read an article that Panera opened a nonprofit restaurant where customers (is that even the right word?) donate however much they can afford, or think the meal was worth.  Want to pay nothing?  You?re free to walk away.  Feel like being philanthropic?  Throw in a C-Note.  It?s an interesting experiment because here in the United States, we?re just not used to that approach.

We Americans like to know up-front how much we?re going to pay for something.  We like to see the price on the menu, and we raise holy hell if the price on the receipt doesn?t match.  We are, in some ways, like a nation of consumer sheep, wishing to be led from one expectation to another.

On a separate tangent, let?s take a look at Amazon?s (up-to-recently) wildly successful Kindle e-reader.  I own a first-generation Kindle, and I love it.  I used to love it more when (like a sheep) I knew that the books on the Kindle would never be more than $9.99, but even with some of their pricing changes, I still love it.  Why?  Convenience.

Amazon requires that you have a credit card on file with them in order to have a Kindle.  That way, when you decide to buy a book, they can instantly charge your card.  Simple for you, and great for Amazon, because you are more likely to buy the book when you don?t have a chance to abandon your shopping cart.

So, what happens if you combine these two ideas?  In my opinion, what you get is the future of paying for media.image

Today, when you ?buy? media, you?re told up front how much it will cost (baaa-a-a-aa), and asked to pay before you consume the media.  This means that you, the consumer, are the one taking the chance. 

How many times have you shelled out twenty bucks or more to go see a movie, only to feel like you not only wasted your money, but also your precious time?  It?s happened to me more often than I?d like to remember.

Now, imagine a world where you got to see the movie first, then decided how much to pay for it.  For some movies you might walk out in disgust, paying nothing, while others you might give extra to encourage the filmmakers to make more just like it. 

This might not be a great model for seeing movies at a theater, but it starts to become a great model for media you consume at home.  Music, video games, movies, short films?they?re all candidates for ?pay what you think it?s worth.?  What we?re missing, though, is an easy way to pay for it.  This is where the Amazon model comes into play. 

Easy, quick and secure is the model that needs to be adopted. 

There?s  a guy on the internet by the name of Patrick Fitzgerald that has put together a great tutorial on CSS.  His tutorial has saved me countless amounts of frustration and tons of time.  In exchange, he asks for nothing but a ?beer? (or, in this case a couple dollar donation through PayPal).

I?m embarrassed to say that even though I feel like his content is worth it, I?ve never bought the guy a beer.  Why?  Because the PayPal donation model is onerous.  If I don?t have a PayPal account, I need to reach into my wallet, pull out the credit card, and type in all that information.  Bozhe Moi!  I might have to take all of three minutes to do this, but in my brain, it?s not worth it, even though Patrick has saved me hours of time.

Yes, even that inconvenience is enough to put the kibosh on my donation.  But if I saw the guy at a convention, I?d gladly reach into my pocket and buy him a beer.  Heck, I might even buy him a meal for the time he?s saved me.

The only way to overcome this with respect to media is to make it one-and-done. . . like Amazon.  And let the consumer decide how much? like Panera.  Sure, you?ll have deadbeats that don?t pay simply because they can get away with it, but you?ll have others that decide to buy a meal instead of a beer, and, hopefully, that will make up for the others.

So there?s the big idea for the technologists.  Implement the payment gateway.  Start the business.  And, if you happen to be successful with this venture, remember where you got the idea and pay me what you think it was worth.

(sheep image is used under a Creative Commons license from Anita Matinz)

23

05/10

When in Doubt, Look at Debug

8:14 am by admin. Filed under: Uncategorized

I?m working on binding some data to a TreeView in WPF.  Though this is a throwaway prototype, I?m still using a MVVM approach to model what it?s going to look like in the real application.

My ViewModel looks something like this.  Yes, some of this code is sloppy.  I blame that on it being a prototype, and it being too close to bedtime.

   public class SourceVizViewModel
    {

        public ObservableCollection<SourceItem> RootTree;

        public SourceVizViewModel()
        {
            RootTree =  new ObservableCollection<SourceItem>();
        }

        public void InitializeStuff()
        {
            ObservableCollection<SourceItem> RootItems = GetItems("RootFolder");

            var TopLevelFolders = from i in RootItems
                                  where i.isFolder == true
                                  select i;

            foreach (SourceItem item in TopLevelFolders)
            {
                ObservableCollection<SourceItem> children =

                                          GetItems(item.ItemName);
                item.Kids = children;
            }

            RootTree.Add(new SourceItem()

                    { ItemName = "$/", isFolder = true,
                      RetrievedChildren = false });
            RootTree[0].Kids = RootItems;

        }

        public ObservableCollection<SourceItem> GetItems(string FolderName)
        { 
            //Some code that returns an ObservableCollection of SourceItems 
     
            return returnValue;
        }
    }

    public class SourceItem
    {
        public ObservableCollection<SourceItem> Kids { get; set; }
        public bool RetrievedChildren { get; set; }
        public bool isFolder { get; set; }
        public string ItemName { get; set; }

        public SourceItem()
        {
            Kids = new ObservableCollection<SourceItem>();
        }
    }

 

In our MainWindow.xaml, we bind a treeview to the RootTree as such:

<TreeView Height="311" HorizontalAlignment="Left" Name="treeView1" VerticalAlignment="Top" Width="412" VerticalContentAlignment="Stretch" HorizontalContentAlignment="Stretch" ItemsSource="{Binding RootTree}" Margin="38,0,0,0">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Kids}">
                    <TextBlock Text="{Binding ItemName}"/>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
</TreeView>

 

In our MainWindow.xaml.cs, we create a new instance of the ViewModel, assign it to the Window?s DataSource, and InitializeStuff().  We hit F5 and voila!  NOTHING!

Why the heck isn?t this working?  For an hour, I pound my head against my keyboard trying things.  I check and double check names to make sure my binding is correct.  Everything matches up perfectly.  I hit F5 one more time, but this time, I happen to be watching the Debug output.

System.Windows.Data Error: 39 : BindingExpression path error: ‘RootTree’ property not found on ‘object’ ”SourceVizViewModel’ (HashCode=52081661)’. BindingExpression:Path=RootTree; DataItem=’SourceVizViewModel’ (HashCode=52081661); target element is ‘TreeView’ (Name=’treeView1′); target property is ‘ItemsSource’ (type ‘IEnumerable’)

 

RootTree not found?   So I check the code one last time . . .

public ObservableCollection<SourceItem> RootTree;

// should have been defined as

public ObservableCollection<SourceItem> RootTree { get; set; }

 

Yes, I know that it needed to be defined as a property, but somewhere in my haste, I mistyped the property shorthand and defined it as a variable.  Sloppy mistake, yes, but what is unforgivable is not watching the Debug output to see where it was choking.

Lesson learned:  When Data Binding to a ViewModel, always look at Debug to find binding mistakes.

23

05/10

TFS error: TF218027 The following reporting folder could not be created on the server

6:39 am by admin. Filed under: Uncategorized

I was playing around with my test instance of Team Foundation Server today, trying to create a new project, when I got error TF218027 when it tried to create the Reporting Services folder for the project.  The strange thing was, this was not my first project created on this server.

I searched the Internet for anything similar, and found a post that said Reporting Services should be run with the NETWORK SERVICE account.  Since this was a hastily put together server, I was running it with the Administrator account, so I tried switching it over.

No dice.  I got the same TF218027 error, but this time it was due to it not being able to decrypt the symmetric keys.  Apparently, it’s a bad thing to change the account on the Reporting Services service.

I hastily changed the account back to Administrator and resarted the service.  Interestingly enough, this seems to have fixed the problem.

17

05/10

A Simple Dark Glossy Button with WPF

4:50 am by admin. Filed under: Uncategorized

There are a ton of sample button styles out there for WPF, but in the course of some development I decided to see what it takes to make my own.  Once I?ve figured something out, I?m be happy to use someone else?s code.  But, until I understand the wiring beneath the surface, I won?t commit to using something that someone else built.

Out of the box, WPF buttons are a modern chrome style that is best suited to a window with a white background.  The application I?m working on is going to have a darker appearance overall, which, unfortunately, makes the chrome-styled buttons really stick out.  So, what I wanted to do was create a button that was slightly more in tune with a darker scheme.

image

As I describe the steps below, keep in mind that I was using Expression Blend 3 to do some of the work.  With the power of XAML, this can all be accomplished in code?in fact, I found some of it easier to accomplish in code than it is to find the correct way in Blend.

Doin? it Yourself

The first thing to do is throw a button on the form, and create an empty template for it.  Do this by right clicking on the button, choosing ?Edit Template? then ?Create Empty.?

image

When prompted, give it a name.  To keep things separated in my solution, I put the style in its own ResourceDictionary, which I cleverly called ?ResourceDictionary1.xaml?.

This creates a nice empty grid template for you.

To work correctly, this button needs two things.  A Rectangle with rounded edges, and a content presenter .  Throw a default sized rectangle into the grid, then a content control.

Set the Rectangle to stretch horizontally and vertically, and set all of the margins to zero.  Set the ContentPresenter to center vertically and horizontally, with its margins set to zero as well.  Make sure you give the ContentPresenter a name (like ?contentPresenter?), so we can reference it in our triggers.  When you are done, your template should look something like this:

image

Sexy, huh?  No, not really.  If you?re doing it through code, your ResourceDictionary should look something like this:

  1. <ResourceDictionary
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  4. <ControlTemplate x:Key="SampleButton" TargetType="{x:Type Button}">
  5. <Grid>
  6. <Rectangle Fill="White" Stroke="Black" Margin="0" VerticalAlignment="Stretch"/>
  7. <ContentPresenter x:Name=?contentPresenter? Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center"/>
  8. </Grid>
  9. </ControlTemplate>
  10. <!-- Resource dictionary entries should be defined here. -->
  11. </ResourceDictionary>
  12.  

Next, tweak the rectangle so that it has acceptably rounded corners, a nice gradient fill, and a light (as in White) one-pixel border.  I?ll leave this exercise up to you. 

You?ll also need to change the font color of the ContentPresenter so it shows well against a dark background.  This is where I ran into a bit of a problem with using Expression Blend.  For the life of me, I couldn?t find where I could set that.  No problem, though, we?ll just go to code.

Edit the XAML for the ContentPresenter to set its TextBlock.Foreground to a lighter color (in this case, White):

  1. <ContentPresenter Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center" TextBlock.Foreground="White"/>

 

At this point, your template should now be resembling something magical:

image

To get the button UI to react when it is clicked, you?ll need to add a property trigger.  Now, you might be asking, ?Why not use an animation??  I guess you could, but what really works here from a UI standpoint is instantly changing the property when the action occurs.  The property trigger also gives you the benefit of reverting to its previous state once the action completes (so you don?t have to have a separate animation to animate when the left mouse button is released.

Setting the Trigger

You can add the trigger using the Blend IDE, or you can simply add the code manually.  Since this is such a minor trigger, I say ?go for the code,? as you?ll understand the workings better.  In your ControlTemplate, add the following XAML.

  1. <ControlTemplate.Triggers>
  2.         <Trigger Property="IsPressed" Value="True">
  3.                 <Setter TargetName="contentPresenter" Property="RenderTransform">
  4.                         <Setter.Value>
  5.                                 <TranslateTransform Y="3.0"/>
  6.                         </Setter.Value>
  7.                 </Setter>
  8.         </Trigger>
  9. </ControlTemplate.Triggers>
  10.  


What this is doing is waiting for IsPressed to be true.  When that happens, we set the RenderTransform property on the contentPresenter to shift down by three pixels.  Adjust your transform to taste (adding a bit of X transform looks nice, too).

Note that the ?TargetName? in the <Setter> tag needs to correspond to the name you gave your ContentPresenter.

The button is usable right now from a UI standpoint.  However, to make things a bit more fancy, we can add an animation when the mouse enters, and reverse that animation when it leaves.

Doing the Animation

For this, I used the Blend UI to create the animation.  It?s just a lot easier that way.  For the mouse enter animation create a new storyboard, and put a key frame for the rectangle in your template at time zero.  Move the yellow timer bar forward by one or two tenths of a second, and move the bottom of the gradient up ever so slightly using the ?giant arrow? tool in Blend.  This should add a second key frame for your animation.

image

Now, click on the little down arrow next to the plus sign on the ?Objects and Timeline? window. image  Using the menu, duplicate the animation, then reverse it, and finally rename it.  In my case, I used MouseEnter and MouseLeave as my animation names.

The last step is to wire up the events to the animation.  Do this using the Triggers tab.

image

Click on the ?+ Event? button to add a new event.  You?ll be given a default event of ?When target-element.Loaded is raised.?  Change this so it reads ?When target-element.MouseEnter is raised,? and click the plus button next to it.  Change the added action to begin your MouseEnter storyboard.

Finally, do the same for MouseLeave, and have it begin your MouseLeave storyboard.  At this point, you should have a working style that is already applied to the button you originally put on the form.  You can add additional buttons to the form and choose whether or not you want to apply that style, simply by dragging the style onto the button.

The final style code in the Resource Dictionary should now look something like this:

<ResourceDictionary

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

        <ControlTemplate x:Key="SampleButton" TargetType="{x:Type Button}">

                <ControlTemplate.Resources>

                        <Storyboard x:Key="MouseEnter">

                                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Offset)">

                                        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="1"/>

                                        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="0.606"/>

                                </DoubleAnimationUsingKeyFrames>

                        </Storyboard>

                        <Storyboard x:Key="MouseLeave">

                                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangle" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Offset)">

                                        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.606"/>

                                        <SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="1"/>

                                </DoubleAnimationUsingKeyFrames>

                        </Storyboard>

                </ControlTemplate.Resources>

                <Grid>

                        <Rectangle x:Name="rectangle" Stroke="#FFF1F1F1" Margin="0" VerticalAlignment="Stretch" RadiusX="8.5" RadiusY="8.5">

                                <Rectangle.Fill>

                                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">

                                                <GradientStop Color="Black" Offset="1"/>

                                                <GradientStop Color="#FF5A5A5A"/>

                                        </LinearGradientBrush>

                                </Rectangle.Fill>

                        </Rectangle>

                        <ContentPresenter x:Name="contentPresenter" Margin="0" HorizontalAlignment="Center" VerticalAlignment="Center" TextBlock.Foreground="White"/>

                </Grid>

                <ControlTemplate.Triggers>

                        <EventTrigger RoutedEvent="Mouse.MouseEnter">

                                <BeginStoryboard Storyboard="{StaticResource MouseEnter}"/>

                        </EventTrigger>

                        <EventTrigger RoutedEvent="Mouse.MouseLeave">

                                <BeginStoryboard x:Name="MouseLeave_BeginStoryboard" Storyboard="{StaticResource MouseLeave}"/>

                        </EventTrigger>

                        <Trigger Property="IsPressed" Value="True">

                                <Setter TargetName="contentPresenter" Property="RenderTransform">

                                        <Setter.Value>

                                                <TranslateTransform Y="3.0"/>

                                        </Setter.Value>

                                </Setter>

                        </Trigger>

                </ControlTemplate.Triggers>

        </ControlTemplate>

        <!– Resource dictionary entries should be defined here. –>

</ResourceDictionary>

16

05/10

WPF Checked Listbox

8:44 am by admin. Filed under: Uncategorized

I’m working on a little WPF application, and needed to bind some data to a checked listbox.  Using the basics that I found on merril.net, I constructed my checked listbox and bound the data to an ObservableCollection.

The codde below shows the ListBox control in XAML and my binding.  I’m using a ViewModel assigned to the DataContext of the control, and “Files” is an  ObservableCollection<VersionedFile>.

 

 

This works great when I need to clear and re-add all of the items in the colleciton, but if I need to go through and programmatically “Select All” of the files, I run into a bit of an issue . . .   The changes don’t show up.

The problem is that the ObservableCollection only notifies the bound control when an item is added or deleted.  It doesn’t notify when one of the collection’s item’s properties change.  So, logic like this is not reflected at all in the UI:

 

 

In order to get this to work, the IsChecked property needs to be implemented in a class that supports the INotifyPropertyChanged interface.  This allows the binding to work correctly and for the notifications to be sent when the collection item’s property changes.

 

The INotifyPropertyChanged implemenation looks something like what you see below.  Because IsChecked is implemented to call OnPropertyChanged, changes to IsChecked will be reflected in the UI.  However, because FileName is not implemented this way, changes to FileName will not be updated automatically in the UI. 

 

Older Posts »