WPF Reusing Xaml Effectively
I've recently been working on a project using WPF to produce a diagram. In this I must show text alongside symbols that illustrate information associated with the text.
To draw the symbols I initially used some png images I had produced. Within my diagram these images appeared blurry and only looked worse when zoomed in on. To improve on this I decided I would use a vector rather than a rastor image format. Below is the method I used to get the rastor image from a file path:
protected Image GetSymbolImage(string symbolPath, int symbolHeight)
{
Image symbol = new Image();
symbol.Height = symbolHeight;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.UriSource = new Uri(symbolPath);
bitmapImage.DecodePixelHeight = symbolHeight;
bitmapImage.EndInit();
symbol.Source = bitmapImage;
return symbol;
}
Unfortunately this does not recognise vector image formats. So instead I used a method like the following, where "path" is the file path to a vector image of the format开发者_运维技巧 .xaml:
public static Canvas LoadXamlCanvas(string path)
{
//if a file exists at the specified path
if (File.Exists(path))
{
//store the text in the file
string text = File.ReadAllText(path);
//produce a canvas from the text
StringReader stringReader = new StringReader(text);
XmlReader xmlReader = XmlReader.Create(stringReader);
Canvas c = (Canvas)XamlReader.Load(xmlReader);
//return the canvas
return c;
}
return null;
}
This worked but drastically killed performance when called repeatedly.
I found the logic necessary for text to canvas conversion (see above) was the main cause of the performance problem therefore embedding the .xaml images would not alone resolve the performance issue.
I tried using this method only on the initial load of my application and storing the resulting canvases in a dictionary that could later be accessed much quicker but I later realised when using the canvases within the dictionary I would have to make copies of them. All the logic I found online associated with making copies used a XamlWriter and XamlReader which would again just introduce a performance problem.
The solution I used was to copy the contents of each .xaml image into its own user control and then make use of these user controls where appropriate. This means I now display vector graphics and performance is much better.
However this solution to me seems pretty clumsy. I'm new to WPF and wonder if there is some built in way of storing and reusing xaml throughout an application?
Apologies for the length of this question. I thought having a record of my attempts might help someone with any similar problem.
Thanks.
What you're fundamentally doing, when you wrap your Canvas
in a UserControl
, is creating a FrameworkTemplate
. A FrameworkTemplate
is an abstract class that, saith the documentation, "enables the instantiation of a tree of FrameworkElement
and/or FrameworkContentElement
objects," which is your real objective here.
There are two concrete subclasses of FrameworkTemplate
: ControlTemplate
and DataTemplate
. Creating a UserControl
in XAML sets its Content
, which is used to construct its ControlTemplate
, so that every time you instantiate the UserControl
it instantiates all of the objects in that template.
You could instead create a ControlTemplate
and then use it when creating some other kind of control. Or - and this is probably the best approach - you could create a DataTemplate
.
For instance, consider this XAML:
<DataTemplate TargetType="Symbol">
<Canvas Canvas.Top="{Binding Top}" Canvas.Left="{Binding Left}">
<!-- XAML to construct the symbol goes here -->
</Canvas>
</DataTemplate>
Now you can do this:
<ItemsControl ItemsSource="{StaticResource SomeCollectionOfSymbolObjects}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
This will create a Canvas
(your symbol) in the ItemsControl
's Canvas
for every Symbol
in the collection, positioned where the Symbol.Top
and Symbol.Left
properties say it should be positioned.
With a little monkeying around with template selectors and the proper design of the Symbol
class, you can probably use data-binding to construct the entirety of your diagram.
Edit
There are other subclasses of FrameworkTemplate
besides ControlTemplate
and DataTemplate
. One appears in this very post.
Steve,
while this might not answer your whole question or solve your problem directly, it might help you with "storing and reusing xaml": You can dynamically load XAML or write objects to XAML using the XamlReader and XamlWriter Classes. I can't estimate if you would actually gain a performance advantage but maybe it's worth a try.
Example from MSDN:
// Create the Button.
Button origianlButton = new Button();
origianlButton.Height = 50;
origianlButton.Width = 100;
origianlButton.Background = Brushes.AliceBlue;
origianlButton.Content = "Click Me";
// Save the Button to a string.
string savedButton = XamlWriter.Save(origianlButton);
// Load the button
StringReader stringReader = new StringReader(savedButton);
XmlReader xmlReader = XmlReader.Create(stringReader);
Button readerLoadButton = (Button)XamlReader.Load(xmlReader);
Best regards
精彩评论