Assign the content of xaml file to App.ViewModel property? - xaml

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;
}
}

Related

Updating a property in a viewmodel of popup doesn't update the UI

As in the title I have a problem where updating a property in a viewmodel of popup doesn't update the UI. I use popups from xamarin community toolkit. I'm using a command that does this task:
async Task ShowPopup()
{
MessagingCenter.Send(AnimeGroupObservable, "AnimeGroups");
Shell.Current.ShowPopup(new MediaListGroupsPopup());
}
It sends a message with payload and shows popup. This is popup viewmodel:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Input;
using OtakuApp.Models;
using Xamarin.Forms;
namespace OtakuApp.ViewModels
{
class MediaListGroupsPopupViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public ObservableCollection<Group> _AnimeGroups = new ObservableCollection<Group>();
public ObservableCollection<Group> AnimeGroups
{
get => _AnimeGroups;
set
{
if (_AnimeGroups == value)
return;
_AnimeGroups = value;
OnPropertyChanged();
}
}
public String _label;
public String label
{
get => _label;
set
{
if (value == _label)
return;
_label = value;
OnPropertyChanged();
}
}
public MediaListGroupsPopupViewModel()
{
MessagingCenter.Subscribe<ObservableCollection<Group>>(this, "AnimeGroups", (AnimeGroupObservable) =>
{
Console.WriteLine(AnimeGroupObservable[0].Name);
label = AnimeGroupObservable[1].Name;
MessagingCenter.Unsubscribe<ObservableCollection<Group>>(this, "AnimeGroups");
});
}
}
}
I'm planning on having a small collection view of labels to select from. But right now I'm struggling to update one label just for testing purposes, so you can imagine that I've tried collection view and it didn't work. Setting _label to something manually in the code shows that binding works. It's just not updating for some reason.
Popup xaml file:
<?xml version="1.0" encoding="utf-8" ?>
<xct:Popup
x:Class="OtakuApp.Popups.MediaListGroupsPopup"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:xct="http://xamarin.com/schemas/2020/toolkit"
Size="300,300">
<StackLayout>
<Label Text="{Binding label}" />
</StackLayout>
</xct:Popup>
So right now I have two problems:
Label doesn't update. It's binded to a property that has INotifyPropertyChanged
Weirdly this subscription happens only the second time (and after that too, just not the first time) I open up a popup. Is this because it's in the constructor? If yes, what's the correct way to deal with it?
Also a small question - I have unsubscribe at the end of subscription. When I didn't have it and I printed out AnimeGroupObservable[0].Name, the first time it was printed one time, the second time I open up the popup two times etc. Is the unsubscribe at the end the correct way to fix this?
since you are passing a single parameter to a single page, using the constructor would be much simpler than MessagingCenter (which is great, but overkill for this scenario)
when creating the page, pass the parameter in the constructor
Shell.Current.ShowPopup(new MediaListGroupsPopup(AnimeGroupObservable));
then modify the page constructor to accept the parameter
public MediaListGroupsPopup(ObservableCollection<Group> groups)
{
// you did't show how you create your VM, but I assume it's something like this
this.BindingContext = new MediaListGroupsPopupViewModel(groups);
}
then modify your VM constructor
public MediaListGroupsPopupViewModel(ObservableCollection<Group> groups)
{
label = groups[1].Name;
}
if you really are only using a single string value, you could just pass that instead of the entire ObservableCollection

Xamarin Forms Dynamically Load Content in a Page

My current set up:
Xamarin Forms, consisting of iOS, Android, WP app and shared PCL.
Using MVVM Light to keep a nice separation of concerns.
Brief intro into what I want to achieve. I want to have a Base page that has a Cancel and Next button. On pressing the Next button Content is loaded dynamically within that base page.
Xaml View:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="LogInPresenterView">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Cancel" Priority="0" Order="Primary" Command="{Binding Cancel}"></ToolbarItem>
<ToolbarItem Text="Next" Priority="1" Order="Primary" Command="{Binding Next}"></ToolbarItem>
</ContentPage.ToolbarItems>
</ContentPage>
ViewModel Code:
public class LogInPresenterViewModel : ViewModelBase
{
public LogInPresenterViewModel() {}
private RelayCommand _next;
public RelayCommand Next
{
get
{
return _next ?? (_next = new RelayCommand(async () => await DoNext()));
}
}
private async Task DoNext()
{
// IN HERE I WOULD LIKE TO DYNCAMICALLY LOAD CONTENT / VIEWS
}
}
Usually you would have a StackLayout etc before the element. However, on clicking the Next Toolbar Item I want to dynamically load content (that has a viewmodel).
So maybe my ICommand for my next button checked to see what the current content type was, and depending on that I would load another bit of content.
The scenario would be, the base page would load along with the first bit of content - Enter Email and Password. User enters that then clicks on next, if all ok, the content is replaced with the option to enter a security code, keeping the base Close and Next buttons at the top.
Hopefully this makes sense. I know what I want to do in my head, I just don't know how to translate that into Xamarin Forms...
Ok,
So first job is to create your region service in your PCL. This will look something like this:
using System;
using System.Collections.Generic;
namespace xxx
{
public class RegionService : IRegionService
{
private Dictionary<string, object> _regionDictionary;
public RegionService ()
{
_regionDictionary = new Dictionary<string, object> ();
}
#region IRegionService implementation
public bool RegisterRegion (string regionName, object regionObject)
{
object region = null;
_regionDictionary.TryGetValue (regionName, out region);
if (region != null)
_regionDictionary [regionName] = regionObject;
else
_regionDictionary.Add (regionName, regionObject);
return true;
}
public object ResolveRegion (string regionName)
{
object region = null;
_regionDictionary.TryGetValue (regionName, out region);
if (region == null)
throw new RegionServiceException ("Unable to resolve region with given name");
return region;
}
#endregion
}
}
This when you create your page with the dynamic content register your dynamic contentview in your code behind:
ContentView contentView = this.FindById<ContentView>("myContentView");
regionService.RegisterRegion("DynamicView", contentView);
You'll need to create an interface for your views and pages to use to indicate which region they wish to be presented in:
using System;
namespace xxx
{
public interface IRegionView
{
string GetRegionName ();
}
}
Then in your code behind for your view implement this interface to return the name of the region to display in.
You now need a custom presenter to use this region code. I use MVVMCross, so the details will vary for the MVVM implementation you are using, but essentially something like this is what you need:
public async static Task PresentPage(Page page)
{
if (typeof(IRegionView).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
{
IRegionService regionService = Mvx.Resolve<IRegionService>();
string regionName = (page as IRegionView).GetRegionName();
Page region = regionService.ResolveRegion(regionName) as Page;
if (typeof(IModalPage).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
await region.Navigation.PushModalAsync(page);
else if (typeof(IPopupPage).GetTypeInfo().IsAssignableFrom(page.GetType().GetTypeInfo()))
region.PushOverlayPage(page);
else if (typeof(NavigationPage).GetTypeInfo().IsAssignableFrom(region.GetType().GetTypeInfo()))
await (region as NavigationPage).PushAsync(page);
}
}
I hope this is useful for you :)
So if this was me. I would create a region service where the contentview registers a unique region name.
Content would then be marked to use that region, and a custom presenter can be used to show the view model's content in the appropriate region.
I'm on my phone whilst travelling at the moment but I can post some code later on if that helps :)
Tristan
You can dynamically load Xamarin Forms UI with XAML.
Old Answer:
This can be achieved with the use of the LoadFromXaml method. It works in the same was as XamlReader.Load in Silverlight/WPF. It is a hidden method that can be only accessed through reflection. There is an article on how to do it here:
http://www.cazzulino.com/dynamic-forms.html
But, I would like to ask to you go to this feature request at Xamarin and ask that the method be made public so that it becomes a fully supported feature:
https://forums.xamarin.com/discussion/comment/252626

Silverlight for Windows Embedded: How to propagate custom property value from xaml to VS project embedded application

I am working on a Silverlight for Windows Embedded project. I defined a custom user control which consists of a TextBlock control and an image control. I want to specify different text in different instance of the custom control. So I did the following in Expression Blend:
In Blend code-behind (C#) UserControl1.xaml.cs I defined and registered a DependencyProperty and set DataContext of the TextBlock:
namespace WindowsEmbeddedSilverlightApplication1
{
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
ItemText.DataContext = this;
}
public String MyText
{
get { return (String)GetValue(MyTextProperty); }
set { SetValue(MyTextProperty, value); }
}
public static readonly DependencyProperty MyTextProperty =
DependencyProperty.Register("MyText", typeof(String), typeof(UserControl1), null);
}
}
In UserControl1.xaml:
<UserControl
......
x:Class="WindowsEmbeddedSilverlightApplication1.UserControl1"
d:DesignWidth="196" d:DesignHeight="85">
<Grid x:Name="LayoutRoot">
<Image x:Name="ItemImage" Margin="0,0,90,0"/>
<TextBlock x:Name="ItemText" HorizontalAlignment="Right" Width="68" Text="{Binding MyText}" TextWrapping="Wrap" Height="23" VerticalAlignment="Bottom"/>
</Grid>
</UserControl>
To use the custom user control in MainPage.xaml:
<UserControl
......
xmlns:local="clr-namespace:WindowsEmbeddedSilverlightApplication1"
x:Class="WindowsEmbeddedSilverlightApplication1.MainPage"
Width="640" Height="480">
<Grid x:Name="LayoutRoot" Background="White">
<local:UserControl1 HorizontalAlignment="Left" Margin="94,117,0,0" Width="196" Height="85" VerticalAlignment="Top" MyText="Text1"/>
<local:UserControl1 HorizontalAlignment="Left" Margin="94,217,0,0" Width="196" Height="85" VerticalAlignment="Top" MyText="Text2"/>
</Grid>
</UserControl>
So with these when I run the application in Expression Blend I am able to see the correct text displayed on the two instances of the custom user control.
Then I import the project into Visual Studio Silverlight for Windows Embedded Application. I read some posts mentioning that I have to redo the registeration. So I did the following:
In UserControl1.h:
static HRESULT Register()
{
HRESULT hr = E_FAIL;
hr = XRCustomUserControlImpl<UserControl1>::Register (__uuidof(UserControl1), L"UserControl1", L"clr-namespace:WindowsEmbeddedSilverlightApplication1");
hr = RegisterDependencyProperties();
return hr;
}
static HRESULT RegisterDependencyProperties();
HRESULT SetMyText(WCHAR* pText);
HRESULT GetMyText(BSTR* pbstrText);
public:
static DEPENDENCY_PROPERTY_ID m_dpMyTextID;
In UserControl1.cpp:
HRESULT UserControl1::RegisterDependencyProperties()
{
HRESULT hr = E_FAIL;
IXRApplication* pApplication = NULL;
XRDependencyPropertyMetaData dpm = XRDependencyPropertyMetaData();
dpm.Size = sizeof(XRDependencyPropertyMetaData);
dpm.pfnPropertyChangeNotification = NULL;
dpm.pfnTypeConverter = NULL;
dpm.pfnEnumerableCreation = NULL;
XRValue defValue;
defValue.vType = VTYPE_READONLY_STRING;
defValue.pReadOnlyStringVal = L"Default";
dpm.DefaultValue = defValue;
hr = GetXRApplicationInstance(&pApplication);
hr = pApplication->RegisterDependencyProperty(L"MyText", VTYPE_BSTR, ControlID(), &dpm, &m_dpMyTextID);
pApplication->Release();
return hr;
}
HRESULT UserControl1::SetMyText( WCHAR* pText )
{
HRESULT hr = E_FAIL;
XRValue xrValue;
xrValue.vType = VTYPE_READONLY_STRING;
xrValue.pReadOnlyStringVal = pText;
hr = SetPropertyValue(m_dpMyTextID, &xrValue);
return hr;
}
HRESULT UserControl1::GetMyText( BSTR* pbstrText )
{
HRESULT hr = E_FAIL;
XRValue xrValue;
hr = GetPropertyValue(m_dpMyTextID, &xrValue);
if (SUCCEEDED(hr))
{
*pbstrText = xrValue.bstrStringVal;
}
return hr;
}
I didn't change anything in the generated MainPage.h and MainPage.cpp code.
Compilation is successful, and execution is also ok. The custom controls are displayed, but the texts that I put in the xaml are not displayed.
Am I doing something wrong or missing something? I couldn't find much information on this topic on the Internet. Could anyone point me in the right direction. Thanks!
Problem solved.
I added a callback to XRDependencyPropertyMetaData.pfnPropertyChangeNotification:
dpm.pfnPropertyChangeNotification = NamePropertyChanged;
According to MSDN, PFN_PROPERTY_CHANGE is a callback method that XAML for Windows Embedded invokes when the value of the associated property changes. Indeed this callback is called during initialization. In the callback, I set the TextBlock text to the new value:
void UserControl1::NamePropertyChanged(__in IXRDependencyObject* pControl, __in XRValue* pOldValue, __in XRValue* pNewValue)
{
HRESULT hr;
if (pControl && pOldValue && pNewValue)
{
UserControl1 *tempObj;
pControl->QueryInterface(__uuidof(UserControl1), (void**)&tempObj);
hr = tempObj->m_pItemText->SetText(pNewValue->pReadOnlyStringVal);
}
}
So the way of binding DependencyProperty value to a control in C++ code-behind is different from C# code-behind.

Access resx in application from Silverlight class library

Resource files in Silverlight can be accessed using the code below:
ResourceManager rm = new ResourceManager("MyLibraryNamespace.MyFolder.MyResources", Assembly.GetExecutingAssembly());
However in my application this piece of code is not in the application itself, but in a Silverlight class library and the app has reference to it; changing the namespace to the "MyAppNamespace" just generates error.
How can I reach the resources in the xap file from the Silverlight class library?
There is a nice video here: http://msdn.microsoft.com/en-us/hh336287
The trick is to write a "proxy" class so that you can reference strings from XAML. From MSDN:
public class LocalizedStrings {
public LocalizedStrings() { }
private static sdkGlobalizationCS.AppResources localizedResources = new sdkGlobalizationCS.AppResources();
public sdkGlobalizationCS.AppResources LocalizedResources { get { return localizedResources; } }
}
And in XAML (after adding the class in the static resources):
<ListBoxItem Content="{Binding Path=LocalizedResources.LangRegionNameFrFR, Source={StaticResource LocalizedStrings}}" />
This is good and I was able to do the same.
In my case I have the same library shared between applications so I extract dynamically the name of the assembly:
var ast = assembly.FullName;
char[] delimit = new char[] { ',' };
string[] parts = ast.Split(delimit);
var gResources = new System.Resources.ResourceManager(parts[0]+"resource path here", assembly);
In order to achieve what I wanted I had to do the following:
var assembly = Application.Current.GetType().Assembly;
And after that I can create ResourceManager with the resources like this:
var rm = new System.Resources.ResourceManager(name, assembly);
where name is the path from my first post.

Changing/Adding Resource Dictionary in code behind, Please help

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.