Showing posts with label wpf. Show all posts
Showing posts with label wpf. Show all posts

Sunday, November 9, 2008

Studying Paramedicine as a Software Engineer

Paramedic school has been grueling giving the concomitant (bordering on comorbid) factor of work. However, that will be my last complaint on that because as a general rule, when I want to do something, I go and do it. We've finished anatomy and physiology, pathophysiology, medicolegal concerns, various introductory topics, and most recently pharmocology.

We had to turn in cards on 72 drugs (of the 129 paramedics in NC could give), and I decided to make life easier for myself using a small program. LINQ-to-XML plus WPF (and some regular expressions to parse human readable dosages) allowed me to rapidly transcribe all of the useful information into an XML format. I then put together a quick XSLT file to make an OOXML file that I could print and paste to 3x5 index cards (ed: I left off side effects and had to add those by hand, wraaa!). Did I mention the program quizzes me on trade/generic names and pharmacological class? I'll try and release the quizzing features as a webpage at some point, however, my studies come first.


Right now we're having medical math beaten into us, which isn't particularly hard for those of us with strong mathematics backgrounds (ed: except when you suck at basic math). However, I noticed that many students had a hard time connecting the action of calculating a dosage with the mathematics to do the calculation. I put together a presentation to help bridge that gap, "Visualizing Medical Math" (PPTX). Hopefully this will help folks who are struggling with medical math.

(ed: for those of you without PowerPoint 2007 or access to a viewer, "Visualizing Medical Math" PDF).

Monday, May 12, 2008

WPF Application Quality Guide v0.2

Microsoft has released v0.2 of their WPF Application Quality Guide, which contains some great new items in the application testing section. They go over some interesting ways you can test a GUI application, including media sections of an application. I must say I hadn't thought about taking a screenshot of the desired result and then using an image differencing tool on the user's screenshot as a method of acceptance testing. That section definitely got me thinking on how to streamline our own GUI testing practices (read: nonexistent). I have cleaned up versions of their sample code, and if you'd like it just shoot me an email.

Monday, April 7, 2008

Zooming Objects Inside a WPF ItemsControl

Previously I've shown how to zoom objects in WPF using either an attached property or directly binding a LayoutTransform. If you'd like to zoom objects inside of an ItemsControl, you can use a slight variation on the direct binding strategy.

First you should style the ItemsPanel template for your ItemsControl, in this case a ListBox.
<ItemsPanelTemplate x:Key="ZoomedItemsPanel">
<StackPanel IsItemsHost="True">
<StackPanel.LayoutTransform>
<ScaleTransform
ScaleX="{Binding Path=Value, ElementName=ZoomSlider}"
ScaleY="{Binding Path=Value, ElementName=ZoomSlider}" />
</StackPanel.LayoutTransform>
</StackPanel>
</ItemsPanelTemplate>

Then your ListBox can reference the ZoomedItemsPanel and enjoy zooming on just its contents!
<Label Content="Zoom: "
Target="{Binding ElementName=ZoomSlider}" />
<Slider x:Name="ZoomSlider"
Minimum="0.25"
Value="1"
Maximum="5"
SmallChange="0.5"
LargeChange="1.0" />
<ListBox ItemsPanel="{DynamicResource ZoomedItemsPanel}" />

Wasn't that easy?

Friday, March 7, 2008

WPF TileBrush Nonsense

For the absolute life of me I cannot get a custom TileBrush to tile outside of VS2k8 or Blend 2.5. The basics are that I'm constructing a ruler, which started programmatically after finding the current DPI. When that didn't work, I attempted to do the same thing in straight XAML, which wouldn't be relative to the DPI (thus would only be a nice ruler at 96dpi). Even this would not work. At this point my frustration knows no bounds.

The above is a screengrab from inside VS2k8. The screengrab from Blend 2.5 also shows the ruler tiling. Below is a screengrab during runtime, notice the distinctive lack of tiling, and now it is somehow stretching to Fill.

The tiled brush is a DrawingBrush consisting of a GeometryDrawing with a GeometryGroup of LineGeometry's. The annotated XAML is given below:
<DrawingBrush x:Key="RulerBrush"
Stretch="None"
TileMode="Tile"
Viewport="0,0,96,16"
ViewportUnits="Absolute"
Viewbox="0 0 1 1"
ViewboxUnits="RelativeToBoundingBox">
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<GeometryGroup>
<LineGeometry EndPoint="0,16"
StartPoint="0,0" />
<!-- ... -->
<LineGeometry EndPoint="48,16"
StartPoint="48,4" />
<!-- ... -->
<LineGeometry EndPoint="96,16"
StartPoint="96,0" />
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Brush="#FF000000"
Thickness="1" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>

And the requisite XAML for the display area:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Test.Window1"
x:Name="Window"
Title="Window"
Width="640"
Height="480">
<DockPanel x:Name="LayoutRoot" LastChildFill="True">
<ToolBarTray x:Name="ToolBarTray"
DockPanel.Dock="Top">
<ToolBar x:Name="MenuToolBar"
ToolBar.Band="0"
Width="{Binding Path=ActualWidth, ElementName=ToolBarTray}" />
</ToolBarTray>
<Rectangle x:Name="RulerY"
DockPanel.Dock="Left"
Height="15" Margin="0,15,0,0"
Fill="{DynamicResource RulerBrush}">
<Rectangle.LayoutTransform>
<RotateTransform Angle="-90.0" />
</Rectangle.LayoutTransform>
</Rectangle>
<Rectangle x:Name="RulerX"
DockPanel.Dock="Top"
Height="15" Margin="0,0,0,0"
Fill="{DynamicResource RulerBrush}"/>
<ScrollViewer Style="{DynamicResource SimpleScrollViewer}"/>
</DockPanel>
</Window>

This is fairly obnoxious!

Friday, February 8, 2008

Writing Quality WPF Applications

I've got together a few resources which are helpful when attempting to write a quality WPF application. While some of them are for C# 2.0 or .Net 2.0, I feel they are just as valid in C# 3.0 and .Net 3.5.

Without further ado, the (currently short) list:
Hopefully in the future there will be more guides for effective WPF development!

Thursday, January 24, 2008

Where the &%$@ is HashSet?

If you've created a .Net 3.5 project in Expression Blend 2 (December Preview) and then opened it up in VS2k8, you may have noticed that using System.Collections.Generic does not give you HashSet.
The type or namespace name 'HashSet' could not be found.
You may be gnashing your teeth over this, but the simple solution is to add System.Core.dll as a reference to your project because it appears Blend 2 leaves this out.

Update: Zoom multiple controls from the same slider in XAML

I've had a serious learning curve with XAML, and frequently find new and better ways of doing things. A good example of this is zooming multiple controls from the same slider. My previous solution used a DependencyProperty and an IValueConverter and was probably slow as dirt if you wired up more than a few controls to get zoomed. A much more succinct, maintainable, and readable solution would be:
<Page class="ZoomTest.Page1" name="page" xmlns="...">
xmlns:local="clr-namespace:ZoomTest"
local:AttachedProperties.Zoom="0.8">
<Grid>
<Grid.LayoutTransform>
<ScaleTransform ScaleX="{Binding Path=Zoom,
ElementName=page}"
ScaleY="{Binding Path=Zoom,
ElementName=page}" />
</Grid.LayoutTransform>
</Grid>
</Page>
And yes, while I do have two bindings, it actually is very fast. One of the big things I've learned is you can really (ab)use the binding system and strangely enough have a faster UI than one you control! In a GUI application I'm designing in WPF, there are over 5000 data bindings on just one page, and yet it is nearly twice as fast as the version where I practically precompute the whole UI! My only guess as to why this is the case is that UIElement's are much more expensive than data bindings.

Another way to tackle the above problem, if say you're on the same UI without Page's, is actually much simpler:
<Window ...>
<DockPanel x:Name="LayoutRoot"
LastChildFill="True">
<StackPanel DockPanel.Dock="Top">
<Slider x:Name="ZoomSlider"
Minimum="1" Value="5" Maximum="10" />
</StackPanel>
<ScrollViewer>
<Grid x:Name="content">
<Grid.LayoutTransform>
<ScaleTransform ScaleX="{Binding Path=Value,
ElementName=ZoomSlider}"
ScaleY="{Binding Path=Value,
ElementName=ZoomSlider}" />
</Grid.LayoutTransform>
</Grid>
</ScrollViewer>
</DockPanel>
</Window>
Using strategies like this you can use less C# and more XAML. I've been able to take over 7000 lines of presentation code written for Java 1.4 (the C# 2.0 code would be roughly the same size) and turn it into roughly 550 lines of XAML and 100 lines of C# 3.0. Expect more little fun things you can do with WPF soon (like making a ListBox do whatever you please).

Thursday, December 13, 2007

Zoom multiple controls from the same slider in XAML

Recently we've been playing around with XAML and WPF at work, and a common situation we run across is wanting to zoom different Page's without having separate zoom functionality. One slider should be enough to zoom any Page we hook up.

The basics start with an attached DependencyProperty called Zoom.
public static class AttachedProperties
{
public static readonly DependencyProperty ZoomProperty
= DependencyProperty.RegisterAttached("Zoom", typeof(double), typeof(UIElement),
new FrameworkPropertyMetadata(1.0,
FrameworkPropertyMetadataOptions.AffectsRender, null /* PropChangedCallback */,
new CoerceValueCallback((obj, value) => (double)value < 0.0 ? 0.0 : (double)value)));

public static double GetZoom(DependencyObject obj)
{
return (double)obj.GetValue(ZoomProperty);
}

public static void SetZoom(DependencyObject obj, object value)
{
double val = Double.Parse(value.ToString());
obj.SetValue(ZoomProperty, val);
}
}
Now we have an attached property we can connect to any UI Element! Notice that I suffixed 'Zoom' with '-Property' for the static variable, yet gave RegisterAttached just 'Zoom'. This is by convention (coincidentally so are the Get/Set pair below the static value).
<Page x:Class="ZoomTest.Page1" x:Name="page" xmlns="...">
xmlns:local="clr-namespace:ZoomTest"
local:AttachedProperties.Zoom="0.8">
<Grid />
</Page>
Now we have a Page that starts with a default zoom property of 80%. However, this does not do us much good. We don't actually tell the page how to zoom anywhere. This is where Binding comes into play.
<Page x:Class="ZoomTest.Page1" x:Name="page" xmlns="...">
xmlns:local="clr-namespace:ZoomTest"
local:AttachedProperties.Zoom="0.8">
<Grid LayoutTransform="{Binding Zoom, ElementName=page}">
</Grid>
</Page>
Now anything inside of the Grid will get a LayoutTransform based on the Zoom property of the Page! Unfortunately the LayoutTransform requires an actual Transform and not a double. Now we could have made the Zoom property a ScaleTransform, but that makes it less useful as an attached property. However, all is not lost:
public class ZoomConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (targetType != typeof(Transform))
throw new InvalidOperationException();

double val = Double.Parse(value.ToString()) / 100.0;
return new ScaleTransform(val, val);
}

public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Now we just have to hook the converter up to the data binding, and viola! We have a Page who's Grid content zooms in and out with respect to a given scale.
<Page x:Class="ZoomTest.Page1" x:Name="page" xmlns="...">
xmlns:local="clr-namespace:ZoomTest"
local:AttachedProperties.Zoom="0.8">
<Page.Resources><local:ZoomConverter x:Key="zoomConverter" /></Page.Resources>
<Grid LayoutTransform="{Binding Zoom, ElementName=page, Converter={StaticResource zoomConverter}}">
</Grid>
</Page>
At this point all we have to do is wire up a Slider (two step process wraaa)!
<Window x:Class="GeometryTest.Window1" x:Name="window"
xmlns="..." local="clr-namespace:ZoomTest"
Title="Zoom Test" Height="480" Width="640">
<DockPanel LastChildFill="True">
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Label>Zoom:</Label><Slider x:Name="zoomSlider" Minimum="1" Maximum="100" Value="50" />
</ToolBar>
</ToolBarTray>
<Frame Source="Page1.xaml" LoadCompleted="ContentFrame_LoadCompleted" />
</DockPanel>
</Window>
...
void ContentFrame_LoadCompleted(object sender, NavigationEventArgs e)
{
Binding binding = new Binding();
binding.Source = zoomSlider;
binding.Path = new PropertyPath("Value");
(ContentFrame.Content as UIElement).SetBinding(AttachedProperties.ZoomProperty, binding);
}
Now, changing the zoom value with the Slider will cause the Grid to zoom in and out! Bonus points for adding a ScrollViewer to the Page to let you actually see the change in size of your Grid.