I'm am currently developing a silverlight application, I am still a beginner with this.
I am wondering if it is possible to change the resource dictionary's source in code behind(C#)
within the App.xaml?
I have tried the code below, but get an exception, i am getting the style folder name from a WCF Service, the variable is called Style(this contains the name of the folder)
ResourceDictionary rDictionary = this.Resources.MergedDictionaries[0];
rDictionary.Source = new Uri(string.Format("Resources/Styles/{0}/Styles.xaml", style), UriKind.Relative);
this.Resources.MergedDictionaries.Add(rDictionary);
I'm getting an error at
rDictionary.Source = new Uri(string.Format("Resources/{0}/Styles.xaml", "Default"), UriKind.RelativeOrAbsolute);
Which reads
System.Exception: Error HRESULT E_FAIL has been returned from a call to a COM component.
at MS.Internal.XcpImports.CheckHResult(UInt32 hr)
at MS.Internal.XcpImports.SetValue(IManagedPeerBase obj, DependencyProperty property, String s)
at MS.Internal.XcpImports.SetValue(IManagedPeerBase doh, DependencyProperty property, Object obj)
at System.Windows.DependencyObject.SetObjectValueToCore(DependencyProperty dp, Object value)
at System.Windows.DependencyObject.SetEffectiveValue(DependencyProperty property, EffectiveValueEntry& newEntry, Object newValue)
at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)
at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet)
at System.Windows.ResourceDictionary.set_Source(Uri value)
at FCStarFish.App..ctor()
Does this work
<Application.Resources>
<ResourceDictionary x:Key="resourcestyles">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary /> <!-- Dummy, this is the one we will replace -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Then we place a ResourceDictionary in [0] (where our dummy ResourceDictionary is).
Load or replace the style dictionary (load it with the default style in Application_Startup in app.xaml.cs)
var rDictionary = new ResourceDictionary();
rDictionary.Source = new Uri(string.Format("/MyApp;component/Resources/Styles/{0}/Styles.xaml", style), UriKind.Relative);
this.Resources.MergedDictionaries[0] = rDictionary;
Replace MyApp with your applications name.
First follow the steps mentioned by NateTheGreat below and prepend your UriStirng with "/". It should look like:
ResourceDictionary rDictionary = this.Resources.MergedDictionaries[0];
rDictionary.Source = new Uri(string.Format("/Resources/Styles/{0}/Styles.xaml", style), UriKind.Relative);
this.Resources.MergedDictionaries.Add(rDictionary);
I just encountered this problem. In my case, the solution was to change the resource dictionary XAML file's build action to "Content", Copy to Output Directory to "Copy if newer", and Custom Tool to an empty string. The defaults were set to Page/Do not copy/MSBuild:Compile, respectively.
Related
I have a very basic Xamarin.Forms app, where I try to access an Image. This works fine when the image is in the necessary folder e.g
\Resources\btc.png for iOS
\Resouces\Drawable\btc.png for Android
Root folder\btc.png for UWP
However when I add a folder into the mix it doesn't display anything:
\Resources\Logos\btc.png for iOS
\Resouces\Drawable\Logos\btc.png for Android
Root folder\Logos\btc.png for UWP
Here is the line I am using:
<Image Source="btc.png" HeightRequest="20" WidthRequest="20" />
Here is the line that doesn't work:
<Image Source="Logos\btc.png" HeightRequest="20" WidthRequest="20" />
I have triple checked all backslashes and spellings/capital letters.
TL;DR: Do not use sub-folders. ;-)
On Android when using subfolders within the Drawable folder (and the pixel density folders, i.e: drawable-xxhpdi), the sub-folder name is ignored but the drawable's ID are generated in the Java R. file (C# Resources. in Xamarin.Android), assuming there is no invalid names or clashing. So in native Android & Xamarin.Android those drawable will have an resource ID (integer-based) assigned to them and are usable.
But, Xamarin.Forms will not be able to find those images as a reverse lookup is used, from Name to Android resource ID and thus will be no match.
Also on iOS, the use of the Resource folder for images via BundleResource is deprecated and you should be using "Asset Catalog Image Sets" instead.
For more info: Images in Xamarin.Forms
Yes unfortunately you can't make use of sub-folders for android images but you can for the other 2 platforms and to account for the difference here's what I typically do.
Use the following AssetPathHelper (modify for your needs, in the below I only use a sub-folder for UWP images, and for lottie animations I use a sub-folder in both UWP and iOS). Also I assume .png, if you use other image types then need to handle that.
public static class AssetPathHelper
{
public static readonly string UWPimgRoot = #"Assets\Images\";
public static readonly string UWPanimRoot = #"Assets\Animations\";
public static readonly string iOSanimRoot = #"Animations/"; //note the different slash here, not sure if it's required but that is what works
public static string GetImageSource(string resourceName)
{
var imgFileName = resourceName + ".png"; //! obviously this requires all images used to be pngs
if (Device.RuntimePlatform != Device.UWP)
return imgFileName;
return UWPimgRoot + imgFileName;
}
public static string GetLottieAnimSource(string resourceName)
{
var animFileName = resourceName + ".json";
switch (Device.RuntimePlatform)
{
case Device.iOS:
return iOSanimRoot + animFileName;
case Device.UWP:
return UWPanimRoot + animFileName;
}
return animFileName;
}
}
which gets used in the following Converter:
/// <summary>
/// Provides the path to the image taking into account the platform specific location.
/// Can be used without a real binding (i.e. when only a parameter is provided)
/// </summary>
public class ImagePathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null)
return AssetPathHelper.GetImageSource(parameter.ToString());
return AssetPathHelper.GetImageSource(value.ToString());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
So now in my XAML I can just do this, either as a real binding if the image will be changing or if not, then just passing the image name as the converter parameter:
<Image
Source="{Binding ConverterParameter=logo_splash, Converter={StaticResource ImagePathConverter}}"/>
my BuildParameter is defined in my xaml build as follows:
<Activity this:Process.AdvancedBuildSettings=
"[New Microsoft.TeamFoundation.Build.Common.BuildParameter(
" { ""Attribute1"": """",
""Attribute2"": ""Value2"",
""Attribute3"": ""Value3"" } "
)]">
Now I want to update the value of Attribute1 of my BuildParameter but I can't figure out how to do it.
It doesn't look like I can use an Assign block because these attributes names are not known by the compiler, so I want to use BuildParameter's SetValue method but I'm not sure how to call this VB code in my xaml.
<Assign DisplayName="Update That Attribute">
<Assign.To>
<OutArgument x:TypeArguments="x:String">[AdvancedBuildSettings.Attribute1]</OutArgument><!-- this throws a compiler error because it doesn't know what Attribute1 is -->
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments="x:String">""NewValue""</InArgument>
</Assign.Value>
</Assign>
Not familiar with XAML, but here is a code snippet that using TFS API in C# to update the parameter name. You can use WorkflowHelpers.DeserializeProcessParameters Method and WorkflowHelpers.SerializeProcessParameters Method to get parameter name, remove it, and add the new parameter name, maybe it can help you something:
string argumentName = "Attribute1";
var process = Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers.DeserializeProcessParameters(BuildDefinition.ProcessParameters);
if (process.ContainsKey(argumentName))
{
process.Remove(argumentName);
process.Add(argumentName, attributeValue);
BuildDefinition.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(process);
BuildDefinition.Save();
}
I was right, the "Assign" workflow tool was not the tool I wanted. I needed to use the "InvokeMethod" workflow tool in order to invoke the SetValue() method of my BuildParameter object in my XAML build.
MSDN InvokeMethod documentation
More details about the properties of InvokeMethod
So my solution looks like this:
<InvokeMethod DisplayName="Invoke That Method" MethodName="SetValue">
<InvokeMethod.GenericTypeArguments>
<x:Type Type="x:String" />
</InvokeMethod.GenericTypeArguments>
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="mtbc:BuildParameter">[AdvancedBuildSettings]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:String">Attribute1</InArgument>
<InArgument x:TypeArguments="x:String">[NewValue]</InArgument>
</InvokeMethod>
This is for my Windows 8 app:
In my object I have a string property that contains the path of the images I want to use.
public String ImagePath
In my XAML I have set up an Image tag with the following binding:
<Image Source="{Binding ImagePath}" Margin="50"/>
When I reference an image that I've included in my project (in the Asset folder) the image displays correctly. The path is: Assets/car2.png
However, when I reference an image that the user selects (using the FilePicker) I get an error (and no image). The path is: C:\Users\Jeff\Pictures\myImage.PNG
Converter failed to convert value of type 'Windows.Foundation.String'
to type 'ImageSource'
Just to add a little more info. When I use the file picker I am converting the file location to a URI:
Uri uriAddress = new Uri(file.Path.ToString());
_VM.vehicleSingle.ImagePath = uriAddress.LocalPath;
Update:
I'm also saving this image path to isolated storage. I think this is where the issue is. I'm able to save the path of the file selected, but when I try to bind to it when I'm reloading the Isolated Storage it doesn't work.
So if I can't use an image outside of the application directory. Is there a way I can save that image and add it to the directory?
I tried creating a BitmapImage property to my model but now I'm getting errors stating that it can't serialize a BitmapImage.
You should Use Converter
public class ImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
MemoryStream memStream = new MemoryStream((byte[])value,false);
BitmapImage empImage = new BitmapImage();
empImage.SetSource(memStream);
return empImage;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You can't use a file path that points outside the app directory. You will need to read in the StorageFile stream that you get from the file picker and assign that stream to an image source - so binding is pretty hard unless you change your model,to have an imagesource property instead.
As mentioned, you cannot use bindings to access the file system directly, even if you grant access via the File Picker. Take a look at the XAML Images Sample at the Dev Center, for a technique you can use.
In a nutshell, you'll use SetSourceAsync to get your file into a BitmapImage and then you can use that as the binding source.
I recently did some work on binding to an ImageSource.
public System.Windows.Media.ImageSource PhotoImageSource
{
get
{
if (Photo != null)
{
System.Windows.Media.Imaging.BitmapImage image = new System.Windows.Media.Imaging.BitmapImage();
image.BeginInit();
image.StreamSource = new MemoryStream(Photo);
image.EndInit();
return image as System.Windows.Media.ImageSource;
}
else
{
return null;
}
}
}
My "Photo" was an image stored in a byte[]. You could either convert your image to a byte[] or maybe try using a FileStream instead (I haven't tested with a FileStream so I can't say if it will work).
I defined my complete viewmodel using XAML:
<local:TestViewModel xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:icColors"
SampleProperty="Sample Text Property Value">
<local:TestViewModel.Questions>
....
</local:TestViewModel.Questions>
</local:TestViewModel>
How can parse this XAML at runtime and set as a property of my application, App.TestViewModel?
You can parse XAML at runtime using the XAMLReader class. Simply parse your XAML using the XamlReader.Load method, then assign it (remembering to cast the result). Here is some example code:
System.Windows.Resources.StreamResourceInfo streamInfo = System.Windows.Application.GetResourceStream(uri);
if ((streamInfo != null) && (streamInfo.Stream != null))
{
using (System.IO.StreamReader reader = new System.IO.StreamReader(streamInfo.Stream))
{
TestViewModel vm = System.Windows.Markup.XamlReader.Load(reader.ReadToEnd()) as TestViewModel;
}
}
I am building a common WP7 assembly which will display common help/about information for my apps, each app assembly will specify a pair of StackPanels which have some of the app specific information (well call em Legal.xaml and WhatsNew.xaml).
Ideally these app specific XAML files should be in a plaintext form (vs something that is instantiated in code) so loadable via HTTP or as an embedded resource string.
Loading the XAML works fine, until I try to break out some of the style definitions into another file, then XamlReader.Load() fails with a note that: “Attribute AboutPageDocs/CommonStyles.xaml value is out of range. [Line: 43 Position: 45]”
That error would happen when loading Legal.xaml, which when we look around like 43 we find where I am attempting to load the ResourceDictionary that now contains the custom styles:
<StackPanel.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="AboutPageDocs/CommonStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</StackPanel.Resources>
Here is the bugger... if simply copy & paste the StackPanel code (which is being loaded dynamically at runtime) and drop it into a UserControl... things work fine.
Short of having to define my styles inline in both Legal.xaml & WhatsNew.xaml... is there any way to have XamlReader.Load() property lookup CommonStyles.xaml?
On the thought that the Source path was not correct, I have tried placing copies of CommonStyles.xaml in various locations through both assemblies... as well as experimented with the pack:// uri syntax... all to no avail thus far.
What am I missing?
As I realized that XamlReader is able to resolve referenced XAML files when they are specified as absolute paths, I looked for a possibility to specify an own context.
I found this working for me, when I specify a ParserContext when calling XamlReader.Load()
public static FlowDocument ReadFlowDocument( FileInfo xamlFile )
{
// Specify a ParserContext.
// It's important to set BaseUri to the file itself
// not to its parent direcory!
ParserContext parserContext = new ParserContext();
parserContext.BaseUri = new Uri( xamlFile.ToString() );
// Create a stream from this file
FileStream stream = new FileStream( xamlFile.ToString(), FileMode.Open );
// Let the XamlReader load and parse the XAML. It will resolve any referenced ResourceDirectories
// specified with a relative path
return (FlowDocument) XamlReader.Load( stream, parserContext );
}