Define a string as a static resource - wpf

IS there a way to define a constant string to be used as a static resource across the whole application?
I am running a Wpf application but there is no main xaml form. The application is a collection of xaml controls handled by a single classic .cs form.

You can define it as an application resource:
<Application x:Class="xxxxxx"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:clr="clr-namespace:System;assembly=mscorlib"
StartupUri="MainWindow.xaml">
<Application.Resources>
<clr:String x:Key="MyConstString">My string</clr:String>
</Application.Resources>
</Application>

Supplemantary to the answer by #FelicePollano - for code indentation to work I put this as a separate 'answer'.
If you happen to have your original constant defined in a .cs-file you can avoid duplicating its value in <Application.Resources> by this:
<x:Static x:Key="MyConstString" Member="local:Constants.MyString" />
For the reference local above to work you need to include the namespace xmlns:local="clr-namespace:Utils" in the tag <Application>.
The cs-class could then look like this:
namespace Utils
{
public class Constants
{
public const string MyString = "My string";
}
}
An example on usage in the xaml-code could then be:
<TextBlock Text="{StaticResource MyConstString}" />

Just add a resource dictionary XAML file, let's say it's named Dictionary.xaml (Visual Studio can create you one automatically)
Then, add your static resource in this dictionary.
To finish, reference the dictionary in all your XAML controls:
<UserControl.Resources>
<ResourceDictionary Source="Dictionary.xaml"/>
</UserControl.Resources>

You can use like this:
First, sample constant variable:
namespace Constants
{
public class ControlNames
{
public const string WrapperGridName = "WrapperGrid";
}
}
And second XAML using:
<TextBlock Text="{x:Static Member=Constants:ControlNames.WrapperGridName}"

Related

Get ResourceDictionary source from static member with design-time support

My basic goal is to have a ResourceDictionary in a dll which I can use in another WPF project via ResourceDictionary.MergedDictionaries. But I don't want to reference the ResourceDictionary by hard-coding the URI in the XAML of the referencing application, I want to instead reference some static member which will provide the URI.
I have some simplified code which is "working", but only at runtime. At design-time it throws errors and I get no IntelliSense support. For this simplified example, everything is in one assembly (no separate dll).
Dic.xaml (the resource dictionary I want to reference):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<SolidColorBrush Color="Blue" x:Key="BlueBrush"/>
</ResourceDictionary>
Foo (the module to hold the static member with the URI):
(VB.NET version)
Public Module Foo
'[VBTest] is the name of assembly
Public ReadOnly Property URI As New Uri("pack://application:,,,/VBTest;component/Dic.xaml")
End Module
(C# version)
public static class Foo
{
//[VBTest] is the name of assembly
public static Uri URI { get; } = new Uri("pack://application:,,,/VBTest;component/Dic.xaml");
}
And then finally, the place in the application where I want to reference the ResourceDictionary:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VBTest">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="{x:Static local:Foo.URI}"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Border Width="100" Height="100" Background="{StaticResource BlueBrush}"/>
</Window>
At design-time, I get two errors:
XDG0062 An error occurred while finding the resource dictionary "".
XDG0062 The resource "BlueBrush" could not be resolved.
However, the project will build and run just fine. And will show the intended blue square.
The question is, how can I get this to work at design-time?
I found a thankfully easy workaround. Maybe not the prettiest, but it is elegant. I took inspiration from this answer to a somewhat related question.
By creating my own class inheriting from ResourceDictionary, I can better control the loading behavior. That sounds like it would be complicated, but really all I had to do was set Source as part of the constructor and everything just worked.
The below is added to the code file (outsite of Module/static class Foo):
Public Class StylesResourceDictionary
Inherits ResourceDictionary
Public Sub New()
MyBase.New()
Source = URI
End Sub
End Class
Note that Source = URI is referencing Foo.URI from the question code.
And then the XAML file becomes:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VBTest">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<local:StylesResourceDictionary/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Border Width="100" Height="100" Background="{StaticResource BlueBrush}"/>
</Window>
And bam, full design-time support without hard-coding the URI into the referencing application. Control of the ResourceDictionary and its URI is now in the domain of the dll, and can be referenced in a (kinda) static fashion using the dedicated class.

Initialize an array from XAML at runtime

I'd like to initialize an array from XAML in runtime, sort of like on Android.
I've tried doing it like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:coll="clr-namespace:System.Collections;assembly=mscorlib">
<coll:ArrayList x:Key="Hello"></coll:ArrayList>
But when I try to load it from XAML like this
new ResourceDictionary { Source = new Uri("Commands/MPC/resources.xaml", UriKind.Relative) }
I get an exception. Not sure if I'm doing that right.
You can't use System.Collections.ArrayList in Silverlight, because Silverlight doesn't have it. See the MSDN documentation for the System.Collections namespace.
One thing you can do is create a subclass of System.Collections.Generic.List<T> that does nothing more than fill in a value for the generic type parameter:
using System.Collections.Generic;
public class MyList : List<object>
{
// Class has empty body
}
Then you can use it in XAML:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mycoll="clr-namespace:MyNamespace;assembly=MyAssembly">
<mycoll:MyList x:Key="Hello"></mycoll:MyList>
</ResourceDictionary>
If you know you're only going to be using this class with objects of type SomeType, you can subclass List<SomeType> instead of List<object>.

How can I build a string resource from other string resources in WPF?

Here's my 'sample code', including what I'm trying to do. Obviously, it doesn't work at the moment, but is there any way I can make it work?
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;assembly=mscorlib"
>
<System:String x:Key="ProductName">Foo</System:String>
<System:String x:Key="WindowTitle">{ProductName} + Main Window</System:String>
</ResourceDictionary>
The only way to add a computed string to a ResourceDictionary in this way is to create a MarkupExtension. Your MarkupExtension would be used like this:
<ResourceDictionary ...>
<sys:String x:Key="ProductName">Foo</sys:String>
<local:MyStringFormatter
x:Key="WindowTitle"
StringFormat="{0} Main Window"
Arg1="{StaticResource ProductName}" />
</ResourceDictionary>
This assumes you have created a MarkupExtension subclass in your "local" namespace named MyStringFormatterExtension that has properties named "StringFormat", "Arg1", "Arg2", etc, and has a ProvideValue() method that does the obvious.
Note that as Aran points out, a Binding using StringFormatter would be a more common way to achieve the same effect, and is generally the better design. The tradeoff is it would not allow the result to be used as part of a ResourceDictionary.

Error when adding code behind for Silverlight resource dictionary: AG_E_PARSER_BAD_TYPE

It should be possible to add a code behind file for a resource dictionary in Silverlight, but I keep getting the same error, thrown from the InitializeComponent method of my App.xaml constructor: XamlParseException: AG_E_PARSER_BAD_TYPE.
The resource dictionary xaml file looks like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Celerior.Annapurna.SL.ProvisiorResourceDictionary"
x:ClassModifier="public">
...
</ResourceDictionary>
If I remove the x:Class attribute everything works fine again (of course, I double-checked the class name and it's correct). My App.xaml file isn't really exciting and just contains a reference to the resource dictionary:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Celerior.Annapurna.SL.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="ProvisiorResourceDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
What am I doing wrong?
Kind regards,
Ronald Wildenberg
Silverlight does not support the x:ClassModifier thats only supported in WPF.
In addition x:Class isn't valid in a Resource dictionary. Certainly when trying to include the Xaml from the resource dictionary as a merged dictionary Silverlight wouldn't know what to do with the x:Class at that point.
Actually the above isn't strictly true x:Class is valid but the way you are including the dictionary in the application dictionary needs tweaking. Let me first just state that there is the assumption here that you actually need to sub-class ResourceDictionary (if not just drop the x:Class).
I'm also going to go out on a limb based on your inclusion of x:ClassModifier that you actually don't have a ProvisiorResourceDictionary.xaml.cs file in your project. Since SL always creates a public partial you need this file to contain at least:-
public partial class ProvisiorResourceDictionary
{
public ProvisiorResourceDictionary()
{
InitializeComponent();
}
}
That said if don't have something like this already then you may as well just drop x:Class altogether.
Now your app.xaml needs to look like this:-
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<common:ProvisiorResourceDictionary />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Instead of trying to import the XAML file as resource via the Source property you now include an instance of the specialised ResourceDictionary.
Is the ProvisiorResourceDictionary class public? If not, maybe you need to specify the x:ClassModifier attribute as well.

Testing a WPF Window with StaticResources

I have a simple Window with a reference to a StaticResource in the App.xaml.
App.xaml resource definition:
<!-- Standard Text Box Style -->
<Style x:Key="textBoxStyleStd" TargetType="{x:Type TextBox}">
<Setter Property="FontSize" Value="14" />
</Style>
Window componets using the resource:
<TextBlock Grid.Column="1" Grid.Row="0" Name="stationIdTitle"
Style="{StaticResource textBlockStyleStd}"
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="{LocText Key=Title, Dict={StaticResource Dictionary},
Assembly={StaticResource Assembly}}"/>
When trying to unit test this Window I get the error:
System.Windows.Markup.XamlParseException: Cannot find resource named
'{textBlockStyleStd}'. Resource names are case sensitive. Error at
object 'stationIdTitle' in markup file
'Zpg;component/guicomponenets/screens/enterstationidscreen.xaml' Line
23 Position 71.
Is there any way around this? My unit test code is:
[Test]
public void TestEnterKeyPressedNoText()
{
IPickingBusinessObject pickingBusinessObject = mock.StrictMock<IPickingBusinessObject>();
EnterStationIdScreen objectUnderTest = new EnterStationIdScreen(pickingBusinessObject);
Assert.AreEqual(Visibility.Visible, objectUnderTest.stationIdError.Visibility);
Assert.AreEqual("werwe", "oksdf");
Replay();
objectUnderTest.EnterKeyPressed();
Verify();
}
Thanks Kent,
I looked at your suggestions and in most scenarios I agree models should be used and tested however, there is some code associated with the controls (e.g. TextBox visibility) I still wanted to test. To get round this you can create an instance of your Application (but not initialize it) and add the resources manually. This does lead to duplication in the App.xaml and the base unit test but this allows me to complete the tests I wanted.
if (Application.Current == null)
{
App application = new App();
#region Add Static Resources from the App.xaml
Style textBoxStyle = new Style(typeof(TextBox));
textBoxStyle.Setters.Add(new Setter(TextBox.FontSizeProperty, 14d));
Style textBlockStyle = new Style(typeof(TextBlock));
textBlockStyle.Setters.Add(new Setter(TextBlock.FontSizeProperty, 14d));
application.Resources.Add("TextBoxStyleStd", textBoxStyle);
application.Resources.Add("TextBlockStyleStd", textBlockStyle);
application.Resources.Add("TextBlockStyleError", textBlockStyle);
application.Resources.Add("Assembly", "Zpg");
#endregion
}
In the context of your unit test, there is no WPF application running. Therefore, the Window won't find the resource.
My way around this would be to not unit test your views. Instead, use MVVM and unit test your view models. If you want to test your views, write integration tests instead. Your integration tests can actually kick off the application and therefore much more closely imitate the real running of your app.
When I use fully qualified assembly names in my app.xaml resource entries, I only need to instanciate the App() class. In this example, all resources lies in the Majesty_of_Omega_GUI assembly, which is referred by the UnitTest.DLL
<Application
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="Majesty_of_Omega.GUI.App"
StartupUri="Pages/MainPage.xaml"
>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Majesty_of_Omega_GUI;component/Resources/MainScreens.xaml" />
<ResourceDictionary Source="pack://application:,,,/Majesty_of_Omega_GUI;component/Resources/PanelResources.xaml" />
<ResourceDictionary Source="pack://application:,,,/Majesty_of_Omega_GUI;component/Simple Styles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
Test function:
[Test]
public void Testfunction()
{
if (Application.Current == null)
{
App application = new App();
}
SomePage page = new SomePage();
Actually, you can use the same Application and if the resources are from the same assembly, you've got to call the InitializeComponents methods to make it works (Source here).
Have a nice day !

Resources