Extracting Propreties to Styles (Xaml processing tool) - wpf

I need to extract the props (Height, Width, HorizontalAligment .. etc) of a control to a style.
Do you guys know any tool to do that?
I have tried Xaml Power tools (nice, but only handles xml like attribute properties for ex: is not recognized)
Also looked over expression blend.. didn't find anything there either.
At least some framework/api for easy parsing of xaml (found Xaml Toolkit, but it remained in CTP version in 2010..)
Thanks!

If you have created an element and specified properties such as the Slider control below.
<Window x:Class="Styling.ExtractStyle"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ViewTemplateSource" Height="300" Width="300">
<Window.Resources>
</Window.Resources>
<Grid Name="g1">
<Slider Name="mySlider" Height="100" VerticalAlignment="Center">
<Slider.Width>200</Slider.Width>
</Slider>
</Grid>
</Window>
You can implement an extension to the FrameworkElement class...
public static class FrameworkElementExtensions
{
public static void SaveElementStyleToFile(this FrameworkElement element, string fileName)
{
if (element != null)
{
XmlWriterSettings settings = new XmlWriterSettings
{
Indent = true,
IndentChars = new string(' ', 4),
NewLineOnAttributes = true
};
StringBuilder strbuild = new StringBuilder();
XmlWriter xmlwrite = XmlWriter.Create(strbuild, settings);
if (xmlwrite != null)
{
XamlWriter.Save(element, xmlwrite);
}
File.WriteAllText(fileName, strbuild.ToString());
}
else
{
throw new Exception("Cannot serialize a null object");
}
}
}
And call the extension method...
mySlider.SaveElementStyleToFile("mySliderStyle.xaml");
This will give you an XML file in the root directory of your application which captures the 'hard-coded' properties. Here's what it outputs...
<?xml version="1.0" encoding="utf-16"?>
<Slider
Name="mySlider"
Width="200"
Height="100"
VerticalAlignment="Center"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" />
You can then morph this file into a persistent style with an editor. Note that the target Framework element must be first run through WPF's two pass layout system for this technique to work.
For example
TextBox t = new TextBox();
t.Height = 20;
t.SaveElementStyleToFile("myfile.xml");
will NOT work for that reason. Short of the conveniences offered by Xaml Power Toys and/or a full Xaml parser, this is likely to be as close as you will get to meeting your requirements...

Related

How Replace Xaml Text with Text based on MarkUp Extensions automatically

We have some translation mark up extension which looks like this:
TextBlock Text="{l:Translate 'My string'}"
and we want (because we could use some other tool for xaml translation) to replace text tags inside of all project xamls automatically.
Is there any way to find out all nodes or attributes with regex or with xml reader/write to implement this case?
Essentially, XAML meets the standard XML, but to work with it, you need external libraries. As an example: Microsoft XAML Toolkit CTP (download). Simple example, which displays a list of items:
// Previously adding the library
using Microsoft.Xaml.Tools.XamlDom;
XamlDomObject rootObject = XamlDomServices.Load("MainWindow.xaml");
foreach (XamlDomObject domObject in rootObject.DescendantsAndSelf())
{
MessageBox.Show(domObject.Type.ToString());
}
Set Background on every Control in your document:
XamlDomObject rootObject = XamlDomServices.Load("MainWindow.xaml");
foreach (XamlDomObject objectNode in
from control in rootObject.DescendantsAndSelf(typeof(Control))
where !control.HasMember("Background")
select control)
{
objectNode.SetMemberValue("Background", "Red");
}
XamlDomServices.Save(rootObject, "NewFile.xaml");
For replace value of Text property, I use example:
private void Window_ContentRendered(object sender, EventArgs e)
{
XamlDomObject rootObject = XamlDomServices.Load("MainWindow.xaml");
foreach (XamlDomObject objectNode in
from control in rootObject.DescendantsAndSelf(typeof(TextBlock))
where control.HasMember("Text")
select control)
{
objectNode.SetMemberValue("Text", "MyInsertedText");
}
XamlDomServices.Save(rootObject, "NewFile.xaml");
}
File Input:
<Window x:Class="XAMLdom.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" ContentRendered="Window_ContentRendered">
<Grid>
<TextBlock Text="SomeText" Width="100" Height="30" />
</Grid>
</Window>
File Output:
<?xml version="1.0" encoding="utf-8"?>
<Window xml:base="file:///C:/Documents and Settings/Kanc/мои документы/visual studio 2010/Projects/XAMLdom/XAMLdom/bin/Debug/MainWindow.xaml" x:Class="XAMLdom.MainWindow" Title="MainWindow" Height="350" Width="525" ContentRendered="Window_ContentRendered" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Text="MyInsertedText" Width="100" Height="30" />
</Grid>
</Window>
We have used regular expressions which is not the best way but we can leave with it.

VS 2012: Failed to create a 'ImageSource' from the text

I am declaring an image within my resource dictionary and then displaying in a user control as follows:
ResourceDictionary.xaml (I am using a style here as I plan to update the image as the user changes what they look at, i.e., company, employee, etc.)
<ImageSource x:Key="CompanyIcon">Images/company_128.png</ImageSource>
<Style x:Key="QuickInfoIcon" TargetType="{x:Type Image}">
<!-- Default Value -->
<Setter Property="Source" Value="{StaticResource CompanyIcon}" />
</Style>
The 'Images' folder is a subfolder of 'Assests'. The 'Assests' folder contains my 'ResourceDictionary.xaml' file and I know the path is correct as I get a designer error if I change the path to something like '../Images/company_128.png'
QuickinfoView.xaml
<UserControl x:Class="SidekickAdmin.Views.QuickInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" d:DesignWidth="500" Height="100"
Background="BlanchedAlmond">
<!-- Setup a grid to house the icon and the info -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Name="InfoIcon">
<Image Style="{StaticResource QuickInfoIcon}" Height="50" Width="50"/>
</Grid>
</Grid>
</UserControl>
When viewing the layout in Visual Studio 2012 designer, everything appears correctly but when I run the program I get an error of "XamlParseException occurred: Failed to create a 'ImageSource' from the text 'Images/employee_128.png'." on the ResourceDictionary line with ImageSource.
If I change ImageSource to use a different image it updates as expected within VS2012 designer but then get the same error when trying to run the program.
I have set the Build Action to 'Embedded Resource' on the Resource.resx file but this hasn't fixed the issue.
Any idea on why I am getting the XamlParseException when I try to run this program?
As a side question, when I incorporate images in my program should the image itself (the file) be visible in the bin/debug folder somewhere or is this information hidden with one of the files in bin/debug?
I had the same issue.
You may have a try for what fixed my issue.
Add the file into your project within the Solution Explorer.
I recommend to add folders too dependend on the relative path of your file to the project file.
Then go to:
BUILD -> clean Solution
Finish.
I, too, ran afoul of this issue. Not a single suggested provided by this or any community, most of which were verbatim statements about PACK uri's and other approaches solved the problem. Sadly, for all the enthusiasm people show in answering, most of them haven't a clue on how to fix it.
Setup: 1 solution, 2 projects.
Project 1 was a class library containing resource dictionary containing a list of BitMapSource entries pointing to the local relative path to the images contained below itself.
Example:
<BitmapSource x:Key="RedoImage">Images/Redo.png</BitmapSource>
Project 2 was a WPF application which referenced that class library and which used the MergedDictionary to load the dictionary from the other assembly:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/WPFResourceLibrary;component/Resources/images.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
In a form in the WPF Application project, I dropped a simple IMAGE control on the form. Under it's source property I could select, under LocalResources, from the list of BitMapSources (by key) from my images.xaml resource dictionary. Making a selection, the image would appear (as expected).
But lo and behold, upon running the application, I would get the dreaded "Failed to create image source from the text ..." message. I fiddled about trying all of the many suggestions, each with no success. Always either the same error, or, no error, but no image, at run time.
Fed up, I produced my own custom control. I chose to NOT override the image source property, so we might use that where appropriate, but extended it by adding a dependency property called ResourceName. This control looks through all of the BitMapSources and ImageSources recorded and available within the current app domain. Bingo, it works.
The code:
<Image x:Class="WPFResourceLibrary.Controls.ImageFromResource"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
>
</Image>
And the Code Behind for it.
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WPFResourceLibrary.Controls
{
/// <summary>
/// ImageFromResource - an extension of the standard Image control that properly handles binding Resource to an Image from a Resource
/// </summary>
public partial class ImageFromResource : Image
{
public ImageFromResource()
{
InitializeComponent();
DependencyPropertyDescriptor imageDescriptor = DependencyPropertyDescriptor.FromProperty(ImageFromResource.ResourceNameProperty, typeof(ImageFromResource));
if (imageDescriptor != null)
{
imageDescriptor.AddValueChanged(this, delegate { SetImage(); });
}
}
public static DependencyProperty ResourceNameProperty = DependencyProperty.Register("ResourceName", typeof(ImageSource), typeof(ImageFromResource), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
[Description("Resource String of the Target Image."), Category("Appearance")]
public ImageSource ResourceName
{
get { return (ImageSource)GetValue(ResourceNameProperty); }
set
{
SetValue(ResourceNameProperty, value);
}
}
private void SetImage()
{
if(ResourceName != null)
this.Source = new BitmapImage(new Uri(ResourceName.ToString()));
}
}
}
Thus, providing the functionality expected of the standard image, without the issues arising.
Note, this was performed in Visual Studio 2012 with SP3.
Paul

Restoring Bindings after XamlReader parsing

my problem is the following: In my program I let the user place shapes (class DrawingShape) on a Canvas. The Drawing Shape encapsulates a stacked path and label:
<UserControl x:Class="HandballTrainerFluent.Graphics.DrawingShape"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="60"
d:DesignWidth="60"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid x:Name="container" Width="Auto" Height="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="38"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Canvas x:Name="geometryCanvas" HorizontalAlignment="Center" Grid.Row="0" Width="38" Height="38">
<Path x:Name="Path"
Width="35.8774"
Height="31.2047"
Canvas.Left="1.0613"
Canvas.Top="3.29528"
Stretch="Fill"
StrokeLineJoin="Round"
Stroke="{Binding OutlineBrush,Mode=OneWay}"
StrokeThickness="{Binding OutlineWidth,Mode=OneWay}"
StrokeDashArray="{Binding OutlinePattern,Mode=OneWay}"
Fill="{Binding FillBrush,Mode=OneWay}"
Data="F1 M 19,3.79528L 1.5613,34L 36.4387,34L 19,3.79528 Z "/>
</Canvas>
<TextBlock x:Name="TextBox" HorizontalAlignment="Center" Grid.Row="1" Text="{Binding LabelText,Mode=OneWay}"></TextBlock>
</Grid>
</UserControl>
So some visual setting and the label text are bound to Properties of the code-behind file.
After deserializing a Canvas with these Drawing shapes, I need to restore the binding between the XAML and the code-behind file. I've tried this, but it does not seem to work:
private void RepairBindingsAfterLoading()
{
foreach (UIElement element in this.drawingCanvas.Children)
{
if (element.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)element;
BindingOperations.ClearAllBindings(shape.Path);
BindingOperations.ClearAllBindings(shape.TextBox);
BindingOperations.ClearAllBindings(shape);
shape.BeginInit();
Binding dataContextBinding = new Binding();
dataContextBinding.RelativeSource = RelativeSource.Self;
shape.SetBinding(DrawingShape.DataContextProperty, dataContextBinding);
Binding fillBinding = new Binding("FillBrush");
shape.Path.SetBinding(Path.FillProperty, fillBinding);
Binding outlineBinding = new Binding("OutlineBrush");
shape.Path.SetBinding(Path.StrokeProperty, outlineBinding);
Binding widthBinding = new Binding("OutlineWidth");
shape.Path.SetBinding(Path.StrokeThicknessProperty, widthBinding);
Binding patternBinding = new Binding("OutlinePattern");
shape.Path.SetBinding(Path.StrokeDashArrayProperty, patternBinding);
Binding labelTextBinding = new Binding("LabelText");
shape.TextBox.SetBinding(TextBlock.TextProperty, labelTextBinding);
shape.EndInit();
shape.UpdateLayout();
}
}
}
No matter what I do to the code-behind Properties (e.g. change FillBrush), the visuals of the displayed DrawingShape won't update. Am I missing an important step here?
I've added shape.BeginUpdate() and shape.EndUpdate() after seeing this question: Bindings not applied to dynamically-loaded xaml
Thanks a lot for any insights
Edit 2012-09-25
Looking at another piece of code which does not depend on any bindings makes me wonder, if I can actually reference any elements from the Xaml-Definition via their x:Name after de-serialization. The following callback does not do anything on a shape:
private void rotateClockwiseMenuItem_Click(object sender, RoutedEventArgs e)
{
if(this.drawingCanvas.SelectedItem.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)this.drawingCanvas.SelectedItem;
TransformGroup transformStack = new TransformGroup();
transformStack.Children.Add(shape.geometryCanvas.LayoutTransform);
transformStack.Children.Add(new RotateTransform(90));
shape.geometryCanvas.LayoutTransform = transformStack;
}
}
Debugging tells me that the contents of shape seem just right. When I execute the command once, shape.geometryCanvas.LayoutTransformis the identity matrix. When executing it a second time, shape.geometryCanvas.LayoutTransform is a TransformGroup of two elements.
It somehow looks like the reference for geometryCanvas (declared in the Xaml) is no the one used on screen.
Got it!
I didn't know that you can't successfully reference x:Name'd XAML elements from outside the code-behind file after de-serialization (that at least seems to be the problem at hand).
A solution is to use FindName() on the UserControl, e.g.:
TextBlock textBox = shape.FindName("TextBox") as TextBlock;
The complete and correct RepairBindingsAfterLoading() looks like this:
private void RepairBindingsAfterLoading()
{
foreach (UIElement element in this.drawingCanvas.Children)
{
if (element.GetType() == typeof(DrawingShape))
{
DrawingShape shape = (DrawingShape)element;
shape.DataContext = shape;
Path path = shape.FindName("Path") as Path;
Binding fillBinding = new Binding("FillBrush");
path.SetBinding(Path.FillProperty, fillBinding);
Binding outlineBinding = new Binding("OutlineBrush");
path.SetBinding(Path.StrokeProperty, outlineBinding);
Binding widthBinding = new Binding("OutlineWidth");
path.SetBinding(Path.StrokeThicknessProperty, widthBinding);
Binding patternBinding = new Binding("OutlinePattern");
path.SetBinding(Path.StrokeDashArrayProperty, patternBinding);
TextBlock textBox = shape.FindName("TextBox") as TextBlock;
Binding labelTextBinding = new Binding("LabelText");
textBox.SetBinding(TextBlock.TextProperty, labelTextBinding);
}
}
}
Just for the record, my clumsy
BindingOperations.ClearAllBindings(shape.Path);
BindingOperations.ClearAllBindings(shape.TextBox);
BindingOperations.ClearAllBindings(shape);
works just like the much more simple and elegant solution suggested by dbaseman with:
shape.DataContext = this;
Hope this helps someone else to avoid my mistake :-)

How to add UserControl to a Panel on a WPF Window

I think I'm missing something that should be obvious here, but I'm drawing a blank on this one.
I've built a very primitive UserControl containing nothing more than a TextBox to use as a log window:
<UserControl x:Class="My.LoggerControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
x:Name="LoggerView">
<Grid x:Name="LayoutRoot">
<TextBox x:Name="LogWindow" AcceptsReturn="True"/>
</Grid>
</UserControl>
I don't expect that to be the best way to do it, but it should be good enough for a prototype.
The code-behind is similarly simple:
public partial class LoggerControl : UserControl, ILogger
{
public LoggerControl()
{
InitializeComponent();
}
private LogLevel level = LogLevel.Warning;
#region ILogger
public LogLevel Level
{
get { return level; }
set { level = value; }
}
public void OnError(string s)
{
if (level >= LogLevel.Error)
LogWindow.AppendText("ERROR:::" + s + "\n");
}
// ...
#endregion
}
The thing I can't figure out is how to add this control to my MainWindow.xaml. Simplifying, lets say my window looks like this:
<Window x:Class="My.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:My"
Title="Test" Height="350" Width="525">
<Grid>
<local:LoggerControl x:Name="LogView" />
</Grid>
</Window>
Even with something so simple, the Designer in Visual Studio 2010 can't load the main window. The error given is:
A value of type 'LoggerControl' cannot be added to a collectionor dictionary of type 'UIElementCollection'.
This error message has only one unrelated hit in the major search engines (plus duplicates) so I haven't found any useful help. Microsoft's own documentation seems to imply that this should work.
Any idea how to solve this?
<UserControl x:Class="My.LoggerControl"
xmlns:local="clr-namespace:My.LogTest"
Looks like you may have made a mistake in the namespacing? LoggerControl is listed as being the namespace My, while you're importing My.LogTest and assigning it to the xml-prefix local. Change this to:
xmlns:local="clr-namespace:My"
And I think it should work. Otherwise, fix the LoggerControl declaration.

GlobalSettings ViewModel

I have some global settings that should come from the ViewModel and should be available to all DataTemplates across all UserControls. It includes things like GlobalButtonMargin, GlobalStrokeWidth or GlobalWorkspaceBackgroundColor. Those things are in the viewmodel because the user can edit these settings at runtime.
How would you go about implementing this in a good MVVM fashion?
I thought about having a Singleton GlobalSettingsViewModel. Is this the preferred approach? If so how can I acess the singleton instance from XAML?
Another way would be to pass the GlobalSettings to all ViewModel instances that exist in my application so I can access from the viewmodels I create DataTemplates for. But that feels unclean.
A third approach would be to ditch the ViewModel approach alltogether define that as XAML resources and set the resources dynamically at runtime using FindResource.
Could you sketch out, how you would design your application to support this scenario?
You could use a static you can read from and bind to, using the x:Static in your XAML. I do not like doing static global settings as it's more of an anti-pattern.
I think you should look into inversion of control/dependency injection. There are many IoC containers out there, but I usually use Unity for my dependency injection. It's available at http://prism.codeplex.com
Using IoC, you could register you settings class, and within your VM that need the data, they can easily pull out the settings you want. Your code would look something similar to this (if using unity).
var vm = container.Resolve<SomeViewModel>();
public class SomeViewModel
{
public SomeViewModel(IUnityContainer container)
{
ISomeSettings settings = container.Resolve<ISomeSettings>();
}
}
EDIT: Or here is another solution you may be looking for:
Create your singleton:
class GlobalSettings : ViewModel
{
private Thickness m_globalGirth;
private static GlobalSettings m_instance = new GlobalSettings();
public GlobalSettings()
{
GlobalGirth = new Thickness(2, 2, 2, 2);
}
public Thickness GlobalGirth
{
get { return m_globalGirth; }
set
{
m_globalGirth = value;
InvokePropertyChanged("GlobalGirth");
}
}
public static GlobalSettings Instance
{
get { return m_instance; }
set { m_instance = value; }
}
}
Then setup your bindings:
<Window x:Class="WpfApplication3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication3="clr-namespace:WpfApplication3"
Title="Window1" Height="300" Width="300" MouseDoubleClick="Window_MouseDoubleClick">
<Window.Resources>
<WpfApplication3:GlobalSettings x:Key="settings" />
</Window.Resources>
<Grid>
<Border BorderThickness="{Binding Source={StaticResource settings}, Path=Instance.GlobalGirth}"
BorderBrush="Black"
Width="100"
Height="100" />
</Grid>
</Window>
I would create the type to represent your ViewModel as a class and then define the instance of it as a resource at the ApplicationLevel. That guarentees a single instance for the entire application and you will now be able to refer to those settings using StaticResource. So for example:
<Application xmlns:myNS="clr-namespace:MyNamespace;assembly=MyAssembly" ...>
<Application.Resources>
<myNS:MySettings x:Key="Settings" />
</Application.Resources>
</Application>
And then in windows/controls/templates/etc. you can access the MySettings instance using:
{Binding Source={StaticResource Settings}, Path=MyProperty}

Resources