« February 2007 | Main | April 2007 »

March 28, 2007

Brrrr ... it's cold out here Freezing my WPF off ...

I posted a few days ago about a technique for loading a BitmapImage in the background. Charles  suggested I try loading (the image) in a background thread and then Freeze the BitmapImage; finally, handing it off to the UI thread.

If you'd like to know more about freezing and WPF, there's a decent article available on MSDN here.

Success! It works. But I discovered something sinister going on. Downright evil.

The new implementation was slower. Way slower. How could that be? I was still using a background thread. The code was simpler. I did notice that I had changed from using the MemoryStream to using the UriSource property. A little bit of experimentation later, I discovered that the StreamSource property was also slow when I passed it a FileStream object. Confused thoroughly at this point, I switched back to the MemoryStream in a last ditched attempt knowing it wouldn't help -- but it did!!!! It was fast again! Here's what I ended up with:

 

        private void DoLoadImage(object o)
        {
            if (!_loading) { return; }

byte[] buffer = File.ReadAllBytes(_filename);
// by reading the data into an in-memory buffer,
we prevent the file from being read in the UI thread -- which speeds up

// access dramatically!
MemoryStream mem = new MemoryStream(buffer);

if (!_loading)
{
mem.Dispose();
return;
}

BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 80;
bi.DecodePixelHeight = 60;
//bi.UriSource = new Uri(_filename); slow slow slow
bi.StreamSource = mem;
bi.EndInit();
bi.Freeze();

_imageSource = bi;
_loading = false;
RaisePropetyChanged("IsLoading");
RaisePropetyChanged("ImageSource");

// if you dispose of the memory stream here, the image will be toast (burnt toast)
// (as the dispatcher won't have run yet).
}


For some reason, the MemoryStream technique blows away the performance of any other option. It's at least 5 - 10 times faster than the other options! If anyone knows why, I'd love to hear it! As you can see though, if you compare to my original post, I've gotten rid of the Dispatcher reference and just directly create the BitmapImage. The key though is that I "Freeze" the BitmapImage so that it can be used outside of the creating thread and back in the UI thread.

WPF Rocks

I'm amazed what can be accomplished in WPF with so little code! I'll be posting in detail about all the little things of this very soon (as there's still one more thing I want to add as a demo), but in 344 lines of C# code, and 314 lines of XAML, I created this tiny little application:

 

It doesn't do much really yet -- but it's still very cool. I've got a custom scroll bar, a small reflection on the photos that are being loaded. An animated wait hourglass that bobbles gently back and forth. Clicking on an image automatically loads a larger view. below. It sizes to fit. It releases the resources smartly when they aren't needed anymore.

The lower part, the large image, is just a simple DataTemplate:

 

    <DataTemplate x:Key="ImageDetailTemplate" DataType="{x:Type local:Photo}">
      <Grid>
        <Image x:Name="imgFull" Source="{Binding
            LargeImageSource}"/>
      </Grid>
    </DataTemplate>

 

I used the property/attribute IsSynchronizedWithCurrentItem on the ListBox to automatically keep the "detail" view in sync with the selected item in the ListBox:

 

      <ListBox Style="{StaticResource PhotoListBoxStyle}" 
ItemsSource="{Binding Photos}" Grid.Row="0" Margin="6,6,6,0" VerticalAlignment="Top" Height="110" IsSynchronizedWithCurrentItem="True" ItemTemplate="{StaticResource PhotoItemTemplate}" ScrollViewer.VerticalScrollBarVisibility="Hidden" BorderBrush="{x:Null}" > <ListBox.Background> <LinearGradientBrush EndPoint="0.466,0.06" StartPoint="0.465,0.877"> <GradientStop Color="#FF000000" Offset="0"/> <GradientStop Color="#FF3E3E3E" Offset="1"/> </LinearGradientBrush> </ListBox.Background> </ListBox>

<ContentControl Grid.Row="1" ContentTemplate="{StaticResource ImageDetailTemplate}"
Content="{Binding Photos}"/>


The biggest section of XAML code as you'll see in a later post is actually the silly scrollbar I created! :)


I'll add a bit of photo metadata display and the ability to select a directory to scan (maybe via drag and drop) and post details soon.

March 27, 2007

Loading BitmapSources in the Background ...

I've hit this problem several times now.

I've wanted to load an image on another thread (other than the UI thread), and then display it in a WPF UI. Bam. Doesn't work in WPF. You have to load/create the Bitmap on the UI thread that will ultimately display the image. If the images are from the network or a slow source, this can really nail performance of the application (as it pauses and hangs trying to load the Bitmaps). Here's a work-around that I'm experimenting with. I'll post a larger sample soon, but I thought I'd post this now:

 

1:         private void DoLoadImage(object o)
2:         {
3:             if (!_loading) { return; }
4:  
5:             // this creates a
                        lot of temporary buffers (should try to reuse them somehow...?)
6:             byte[] buffer = File.ReadAllBytes(_filename);            
7:             // by reading the
                        data into an in-memory buffer
, we prevent the file from being read in the UI thread -- which speeds up
8:             // access dramatically!
9:             MemoryStream mem = 
                        new MemoryStream(buffer);
10:  
11:             if (!_loading)
12:             {
13:                 mem.Dispose();
14:                 return;
15:             }
16:             this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, 
(DelegateZeroParam) delegate()
17:             {
18:                 if (!_loading) {
19:                     mem.Dispose();
20:                     return; 
21:                 }
22:  
23:                 BitmapImage bi = 
                        new BitmapImage();
24:                 bi.BeginInit();
25:                 bi.DecodePixelWidth = 80;
26:                 bi.DecodePixelHeight = 60;
27:                 bi.StreamSource = mem;
28:                 bi.EndInit();
29:                 _imageSource = bi;
30:                 _loading = false;
31:                 RaisePropetyChanged(
                        "ImageSource");
32:             });
33:             
34:             // if you dispose
                        of the memory stream here, the image
                        
will be toast (burnt toast)
35:             // (as the dispatcher
                        won't have run yet).
36:         }

The _loading variable I'm using to decide whether loading should still happen for this object. The object (a "Photo" in this case is a Dependency object so it has access to the Dispatcher. The trick is to load the bytes for the image in a thread, but then in the UI thread actually create the Bitmap. I do that using a slight of hand delegate declared as:

    public delegate void DelegateZeroParam();

Of course, you'd need to wrap the actual call to this method in some background loader thread to make it useful.

This technique comes at the cost of bytes, but it make the UI very snappy when compared to a UI-thread only method. (I'll post more about why I'm raising the "ImageSource" property changed in another post).

 

UPDATE: Charles just suggested I try freezing the BitmapImage. New post here.

March 25, 2007

BitmapImage.UriSource binding doesn't work ...

For whatever reason, WPF/XAML doesn't support direct binding of a Uri object to the UriSource of a BitmapImage object. When run, the application fails with an error, "Property 'UriSource' or property 'StreamSource' must be set."  (Here's a complaint about this on MSDN Forums).

This makes easy displays of image items with a source or Uri property more challenging than it should be. I've found one work-around for now (it's not as snappy as I'd like though if the images are large). As with any binding of images, binding to large images may have a negative impact on application performance as Bitmap's must be created on the UI thread.

Here's how to use my new UriToImageConverter value converter:

First, create, in the appropriate Resources, an instance of the new ValueConverter. 

<local:UriToImageConverter x:Key="LocalUriToImageConverter"/>

Of course, you'll need to declare the new xml namespace (usually in the root element in XAML, such as the Window or Page):

xmlns:local="clr-namespace:MyApplicationNamespace"

Then, in the DataTemplate (which is likely where you'd use this), you'll need to use the converter, not as a converter to a Uri, but to the ImageSource itself:

<Image x:Name="imgPhoto" Width="80" Height="60" Grid.Row="0"
Source="{Binding Path=FilenameUri,
Converter={StaticResource LocalUriToImageConverter}}"
>

Finally, add the C# code below to your project.

The Path in this instance can be either a string or a Uri. The trick is that this converter returns a new ImageSource:

 
1:     public class UriToImageConverter : IValueConverter
2:     {
3:  
4:         public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
5:         {
6:             if (value == null)
7:             {
8:                 return null;
9:             }
10:  
11:             if (value is string)
12:             {
13:                 
                            value = new Uri((string)value);
14:             }
15:  
16:             if (value is Uri)
17:             {                
18:                 BitmapImage bi = 
                            new BitmapImage();
19:                 bi.BeginInit();
20:                 bi.DecodePixelWidth = 80;
21:                 //bi.DecodePixelHeight = 60;                
22:                 bi.UriSource = (Uri)
                            value;
23:                 bi.EndInit();
24:                 return bi;
25:             }
26:  
27:             return null;
28:         }
29:  
30:         public object ConvertBack(object value, Type targetType, object parameter, 
System.Globalization.CultureInfo culture)
31:         {
32:             throw new Exception("The method or operation is not
                              implemented.");
33:         }
34:  
35:     }

Note that once the value is converted to a Uri (if necessary), a new BitmapImage object is created [line 18]. Then, within the appropriate Begin/End Init block [lines 19-23], the UriSource (and an example DecodePixelWidth is set). The new ImageSource (BitmapImage) is returned to the converter. So, the Uri is converted directly to the BitmapImage, with WPF being none-the-wiser!

 

March 23, 2007

Rotated text in a TabControl

It's not obvious how to rotate the text in a WPF TabControl TabItem's Header. However, it is easy. :)

The trick is to adjust the TabItem's HeaderTemplate. I created a simple DataTemplate that rotated the contained content using a LayoutTransform (a RenderTransform would have clipped the content).

 

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Name="Window"
    Title="Window1"
    Width="640" Height="480" >

<Window.Resources>
<DataTemplate x:Key="TabItemHeaderTemplate">
<Grid>
<ContentPresenter Content="{TemplateBinding Content}"
RenderTransformOrigin="0.5,0.5" >
<ContentPresenter.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="-90"/>
</TransformGroup>
</ContentPresenter.LayoutTransform>
</ContentPresenter>
</Grid>
</DataTemplate>
</Window.Resources>

<Grid x:Name="LayoutRoot">
<TabControl Margin="163,47,64,50"
IsSynchronizedWithCurrentItem="True"
TabStripPlacement="Left">
<TabItem Header="A big one"
HeaderTemplate="{DynamicResource TabItemHeaderTemplate}">
<Grid>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="128"
Text="1"
TextWrapping="Wrap"/>
</Grid>
</TabItem>
<TabItem Header="A big two"
HeaderTemplate="{DynamicResource TabItemHeaderTemplate}">
<Grid>
<TextBlock FontSize="128"
Text="2"
TextWrapping="Wrap"
Margin="143.02,83.375,143.02,83.375"/>
</Grid>
</TabItem>
</TabControl>
</Grid>
</Window>

March 14, 2007

GE Advantium Oven Review Revisited

Hundreds of searches every month are directed to my site looking for GE Advantium oven information. The search terms are simple: GE Advantium oven. I'm not sure why my site is so popular for that topic. I did have a post and review in 2004 about it here.

In any case, I'll do a bit of an update after you read the other page if you haven't already.

One question you might ask, now 3 years after buying one: would you buy one again?

Yes, I would buy one again or something similar if I needed to. I find it handy for a number of things and would sorely miss it if I didn't have it available.

It's the simple things that it can do: warm a large frozen muffin in the morning to a nice temperature that tastes like it just came out of the oven, without giving it that microwave taste (2:00-2:15, U=6, L=6, M=4, black metal tray).

I can make awesome garlic bread recipe in about 4 minutes:

  • Using 4 slices of French bread sliced from 1 – 1 ½" thick partially frozen to thawed, use a drizzle of olive oil on each (about a teaspoon), spread around the top (or use melted butter or margarine if you prefer, Olive oil though is likely the best for your health according to what I've read)
  • Sprinkle liberally with garlic powder and any other favorite toppings. We have a large tub of frozen Parmesan cheese from Sam's – I usually add a pinch of cheese to the top of each and spread around (or even from a green can is OK).
  • Optional: For extra yummy, use canned diced tomatoes (or fresh if you have them) and add to the top of each slice of bread before cooking.
  • Pop it in the Advantium (4:00, U=10, L=8, M=2). If the bread is thawed completely, you can usually lower the microwave setting to 1 or zero (and sometimes you'll want to reduce the time a little bit).
  • I often use a 'sub-sandwich' loaf of bread the same way with great results.
  • (Also, you can coat the bottom in olive oil the same as the top, and use the black metal tray with the grill ridges to get a subtle grill effect on the bread).

A good slice of garlic bread can even make a lame bowl of Spaghettio's better.

We still use it for frozen pizza all the time. I add a bit of extra cheese to the typical cheap frozen Tombstone pizza, and a bit of extra spices and let it go its course on the normal setting. I often use the black ridge grill tray with pizza to crisp the crust in a subtle but tastier way.

Mac and cheese can also be kicked up a notch:

  • Make a box of Kraft Macaroni and Cheese (I usually leave out the butter/margarine and substitute light Velveeta for a cheesier 'cheese' – about ½" slice)
  • Optional – add ¼ cup of corn meal and stir well to give it a more 'gritty' cheese texture that couldn't be matched by starting with boxed mac and cheese.
  • Add the results of draining 6-8 ounces of yogurt and making yogurt cheese to the mac and cheese at the end. Stir in till it's well absorbed.
  • Add about ½ cup of shredded cheese (preferably cheddar of your choosing) and stir again till melted.
  • Divide into oven safe dishware. Top with more cheese on each.
  • Put it in the Advantium on the metal tray: 5:00-7:00, U=8, L=5, M=1. It's done when the cheese on the top has started to brown and melt completely (if you use a low fat cheese, this may never happen, so just watch it and decide when it looks decent).
  • Careful – the dishware will be HOT!
  • Sprinkle with fresh herbs or chives and serve (with the garlic bread would be very yummy).

Back to the GE Advantium review.

The thing still bugs us the most is that it's so loud!

It's great though at adding a quick 'browning' to things that would have required a long wait in the oven for the same effect (no preheating). I often finish off foods using the Advantium, melting toppings, etc. that I couldn't easily do with the microwave. I like the fact that I can heat the plate I'm serving on which makes the food seem more "restaurant-served", and keeps the food warmer a bit longer.

If the question is: Should I buy an Advantium oven?

That's tougher, as each person and family is different. Read my first impressions on the page referred above. I know there are two power levels for the oven – 120 and 240 volt. We have a 240 volt oven. It cooks things a lot faster than the lower powered one. I'd highly recommend getting the higher powered one if it's an option for you as part of the benefit of having a specialty oven like this is that it is designed to help you cook faster and easier. Check out the buying tips from GE here. If you have children, especially young children, this appliance needs to be respected more than a microwave. Not only can the food get hot, but the entire interior and plates can be scalding hot. Getting a pizza out of the oven after it's 10 minute cycle (for a frozen 12" pizza) is definitely a hot pad experience. The tray and interior are hot.

I hope this helps those of you stopping in for information about the GE Advantium oven.

One cook book that I'd highly recommend that isn't specifically an Advantium cookbook, but has more than a thousand great recipes (many of which can be cooked in the Advantium with some tweaks) is: The America's Test Kitchen Family Cookbook Revised Edition: Featuring More Than 1,200 Kitchen-tested Recipes, 1,500 Photographs And No-nonsense Equipment And Ingredient Ratings. I've definitely enjoyed it -- there's a lot to learn and many product ratings.

 

If you have recipes you'd like to share with others, see this.

 

Stop!

Before you post a comment here, consider instead posting your comment/question in a Google group I've created:

Google Groups
Heat your Food!
Visit this group

If you post a comment here anyway, I'll likely send you an invite for the group so as to better encourage discussion! :)

March 13, 2007

RoundedRectangle for GDI+ and C#

OK, I've written this code so many times I've lost count. Every time I write it, I loose it. L So, I'm going to post it here hoping that I'll find it if I should ever need it again in the future. I noticed today when I was searching for code that draws a rounded rectangle (RoundRect) in C# in WinForms, that my web site was displaying within the top 10 hits on Google! What's most annoying about that is that it was WPF related use of a rounded rectangle, not Windows Forms!

In any case, here's the code for helping to draw a rounded rectangle in C#. It's a function sorely missing on the Graphics object built into the .NET CLR.

internal static GraphicsPath CreateRoundedRectanglePath(Rectangle rect, int cornerRadius)

{

GraphicsPath roundedRect = new GraphicsPath();
roundedRect.AddArc(rect.X, rect.Y, cornerRadius * 2, cornerRadius * 2, 180, 90);
roundedRect.AddLine(rect.X + cornerRadius, rect.Y, rect.Right - cornerRadius * 2, rect.Y);
roundedRect.AddArc(rect.X + rect.Width - cornerRadius * 2, rect.Y, cornerRadius * 2, cornerRadius * 2, 270, 90);
roundedRect.AddLine(rect.Right, rect.Y + cornerRadius * 2, rect.Right, rect.Y + rect.Height - cornerRadius * 2);

roundedRect.AddArc(rect.X + rect.Width - cornerRadius * 2, rect.Y + rect.Height - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 0, 90);

roundedRect.AddLine(rect.Right - cornerRadius * 2, rect.Bottom, rect.X + cornerRadius * 2, rect.Bottom);
roundedRect.AddArc(rect.X, rect.Bottom - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 90, 90);
roundedRect.AddLine(rect.X, rect.Bottom - cornerRadius * 2, rect.X, rect.Y + cornerRadius * 2);
roundedRect.CloseFigure();
return roundedRect;

}

Call this function with the rectangle (using X, Y, Width and Height), and the corner radius. It returns a GraphicsPath object. To draw the rounded rectangle, use a built in Pen or create your own and call the DrawPath function on the Graphics object.

Make sure you Dispose of the GraphicsPath object once your done.

A handy way is to do something like this:

using(GraphicsPath path = CreateRoundedRectanglePath(myRect, 4))
{
graphics.DrawPath(SystemPens.ControlText, path);
}

March 12, 2007

A Drop, an object, the TreeViewItem and ContainerFromElement Solution

How do you return the actual TreeViewItem on which a user dropped a dragged item (usually started with a DoDragDrop)?

It's not obvious, and I'm not sure this is the best solution. The best option would be, if it worked, to call ContainerFromElement on the TreeView. However, calling that returns the parent of the TreeViewItem in question (actually, it might be many ancestors higher than what you'd expect). I wrote my own simple implementation to work around the issue:

void treeKeywords_Drop(object sender, DragEventArgs e)

{

TreeViewItem item = GetTreeViewItemContainerFromElement(sender as ItemsControl, e.OriginalSource as DependencyObject);

if (item != null)

{

// do something with the header--it contains your wrapped object ..

Keyword k = item.Header as Keyword;

if (k != null)

{

Console.WriteLine(k.Title);

}

}

}

public static TreeViewItem GetTreeViewItemContainerFromElement(ItemsControl itemsControl, DependencyObject element)

{

DependencyObject d = element;

while (d != null && !(d is TreeViewItem))

{

d = VisualTreeHelper.GetParent(d);

}

return d as TreeViewItem;

}

The key is the function GetTreeViewItemContainerFromElement. It loops through Visuals, checking each parent. If a parent is a TreeViewItem, it stops and returns it. From the Drop method, the code gets the OriginalSource (which might be a TextBlock for example), passes it and the tree reference to the function and waits for a response. If a TreeViewItem is located, the Header property contains the original object that was stored in the TreeViewItem (at least when data bound).

March 11, 2007

Any bets on when Apple will take the iPhone off their start page?

Anyone want to place a bet on when the iPhone won't be the main attraction on Apple's home page?

According to the press release from Apple, they announced the iPhone on January 9, 2007. It's been 2 months. Maybe it annoys me because it's not going to be sold for another 4 months. Or, maybe because it's odd that they aren't updating the first thing people see when they get to the web page to keep people coming back.

Extended Mode Listbox selection enhancements in WPF

The standard WPF (.NET 3.0) ListBox with SelectionMode set to Extended wasn't behaving the way I wanted. By default ListBoxItems always toggle selection when the left mouse button is pressed down. That's fine for most listboxes I suppose. However, for a listbox that you want to allow drag and drop, it's extremely annoying to have the item you recently selected, become deselected when you start a drag from that item (click down, and unselected!). I wanted the deselection to happen on the mouse button up, not the down (and a quick study of other applications confirmed this is how most other 'extended-mode' ListBoxes behave).

Here is my current work-around for this problem in C#:

public class PhotoListBox : ListBox 

{ 

protected override DependencyObject
                        GetContainerForItemOverride() 

{ 

return new PhotoListBoxItem(); 

} 

} 

public class PhotoListBoxItem : ListBoxItem 

{ 

private bool _mouseWasDownAndSelected = false; 

protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs
                        e) 

{ 

if (!IsSelected) 

{ 

// it wasn't selected, so just do the normal
                        thing 

base.OnPreviewMouseLeftButtonDown(e); 

return; 

} 

// it was selected, so we're going to totally
                        ignore the mouse down... 

e.Handled = true; 

// but we will mark it ... 

_mouseWasDownAndSelected = true; 

} 

protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs
                        e) 

{ 

// if we were watching this one, we'll unselect
                        it if it were already selected 

if (IsSelected && _mouseWasDownAndSelected) 

{ 

IsSelected = _mouseWasDownAndSelected = false; 

} 

base.OnMouseLeftButtonUp(e); 

} 

} 

In the XAML corresponding to my UI I was changing, the only change that was necessary was to modify the ListBox element to use my new PhotoListBox class. I added a CLR reference to the main class:

xmlns:local="clr-namespace:ExampleNamespaceSameAssembly"

Then, I modified the ListBox element:

<local:PhotoListBox x:Name="listboxPhotos" SelectionMode="Extended" ItemsSource="{Binding Mode=OneWay}" …. />

I've created two new classes: PhotoListBox and PhotoListBoxItem for this example.

When items are added to a ListBoxItem, you can explicitly or implicitly add a ListBoxItem wrapper for each item. In this case, because of the use of databinding in my application, I couldn't easily wrap each item manually (explicitly) in a custom ListBoxItem, so I needed to take a different approach. That approach uses the override method, GetContainerForItemOverride. All that needs to be done in this method is to return a new instance of the custom ListBoxItem, above it's PhotoListBoxItem. WPF takes care of the rest. For each item that was databound into the ItemsSource for my custom ListBox, it's automatically wrapped in the PhotoListBoxItem.

Step 2 in my work was to change the behavior of the standard ListBoxItem. That turned out to be a bit trickier than I had expected. Most natural ways I attempted to solve the problem were prevented by either a lack of overridable methods in the ListBoxItem or publicly available methods on the ListBox itself. Here's how I handled it (refer to the code for the exact detail of course).

The left mouse button is key in changing the behavior, so I knew I needed to override the tunneling-like method (On)PreviewMouseLeftButtonDown. I needed the code to make a simple choice, continue processing the button, or ignore it (Handled = true or false?). It's ignored if the item is currently selected (again, to give that toggle behavior that I actually want: the ListBoxItem should become deselected on the UP only if it was already selected). I added a tracking member variable (_mouseWasDownAndSelected) and in the OnPreviewMouseLeftButtonUp event, toggle the selection as needed and always call the base handler.

Enjoy.

Got time to click today for charity?

Spend a moment today to help others …

Be sure to click the "HELP … button" to count your click on each page right below the main graphic. For example:

Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).