I would like to use new feature of UWP -> x:Bind. In order to that, all my pages need to have ViewModel property (as described in tutorials).
To avoid code duplicity, I have established base class as follows:
public abstract class BasePage<TBaseVM> : Page, where TBaseVM : BaseVM
{
public TBaseVM VM { get; private set; }
protected BasePage()
{
DataContextChanged += (s, e) => VM = e.NewValue as TBaseVM;
}
}
As you can see this BasePage class contains property called "VM" and property is of type BaseVM. Hence, I don't need to define VM property on each derived class.
Then I created derived page 'MainPage' defined in xaml as follows:
<pages:BasePage
x:Class="Realarm.View.Pages.MainPage"
x:TypeArguments="viewModel:MainVM">
By doing that, even Resharper's Intellisense offers me properties from "MainVM" in MainPage.xaml, thus is can write:
<ListView ItemsSource="{x:Bind VM.AlarmsVM}">
Unfortunately, when I try to build the project, I get error in MainPage.g.i.cs:
Severity Code Description Project File Line
Error CS0305 Using the generic type 'BasePage' requires 1 type arguments Realarm D:...\Realarm\obj\x86\Debug\View\Pages\MainPage.g.i.cs 13
Any help?
I got this working using Xamarin.Forms.
Base Page:
public abstract class BaseContentPage<TViewModel> : ContentPage where TViewModel : BaseViewModel, new()
HomePage.cs:
public partial class HomePage : BaseContentPage<HomeViewModel>
HomePage.xaml:
<d:BaseContentPage
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="clr-namespace:Sample.Pages;assembly=Sample"
xmlns:vm="clr-namespace:Sample.ViewModels;assembly=Sample"
x:Class="Sample.Pages.HomePage"
x:TypeArguments="vm:HomeViewModel">
<ContentPage.Content>
</ContentPage.Content>
</d:BaseContentPage>
Just add a x:TypeArguments definition at the top of the XAML:
<v:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:v="clr-namespace:YourApp.Views"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:YourApp.ViewModels"
mc:Ignorable="d"
x:TypeArguments="vm:HomeViewModel"
x:Class="YourApp.MainPage">
Worked for me as well when I set the BindingContext as given below in Base Page's constructor:
public BasePage()
{
BindingContext = new TBaseVM();
}
Related
I'm trying to do a very common usage of Xamarin.Forms ListView, where I have multiple types of items.
I'm using a DataTemplateSelector and defining the different (two at this point) views in my XAML file. That requires referencing the c# code from the XAML code through a namesspace definition. And, that's where I'm stuck.
The error I'm getting is
XFC0000 Cannot resolve type "local:NodeTemplateSelector".
Here is my XAML, condensed:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
...
xmlns:local="clr-namespace:varlist">
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="NoteItem">
...
</DataTemplate>
<local:NodeTemplateSelector x:Key="NodeTemplateKey">
NoteTemplate = "{StaticResource NoteItem}"
ImageTemplate = "{StaticResource ImageItem}"
</local:NodeTemplateSelector>
</ResourceDictionary>
</ContentPage.Resources>
</ContentPage>
And, here is C#, also condensed:
namespace varlist
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ListViewPage : ContentPage
{
...
public class NodeTemplateSelector : DataTemplateSelector
{
public DataTemplate NoteTemplate { get; set; }
public DataTemplate ImageTemplate { get; set; }
protected override DataTemplate OnSelectTemplate (object item, BindableObject container)
{
ListView list = (ListView)container;
if (item is NoteData)
return NoteTemplate;
else // item is ImageData
return ImageTemplate;
}
}
}
}
What do I need to change to get the XAML to recognize NodeTemplateSelector ?
As Jason and Shaw suggested, the NodeTemplateSelector must be in top level class.
Also, another problem I ran into is the syntax in the DataTemplate XAML file. Might as well add this note, in case anyone else has the same trouble:
The NodeTemplateSelector in XAML must be defined as direct content; must be like this:
<local:NodeTemplateSelector
x:Key="NodeTemplate"
NoteItemTemplate="{StaticResource NoteTemplate}"
ImageItemTemplate="{StaticResource ImageTemplate}"
/>
Otherwise you get a rather undecipherable runtime error.
If I need to post more code, then let me know, but I think what I have is sufficient to convey what I am trying to do. I can do it if I don't use XAML to define the page content, but when I try it with XAML, addng the <TViewModel,TDataModel> causes InitializeComponent() and any named elements in the XAML to get flagged as not found.
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class BaseSelectListPage<TViewModel,TDataModel> : ContentPage
where TViewModel : BaseSelectListViewModel<TDataModel>, new()
{
private TViewModel _viewModel { get; set; }
public BaseSelectListPopup()
{
_viewModel = new TViewModel();
BindingContext = _viewModel;
InitializeComponent(); // InitializeComponent gets flagged as not found
ExampleElement.Spacing = 5; // ExampleElement also gets flagged as not found
}
// rest of class
}
I don't know how to define the generic types in XAML, but here is what I tried.
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="IRA.Views.SelectLists.BaseSelectListPage<TViewModel,TDataModel>">
<!-- this is obviously what I don't know how to do ^^^^ -->
<ContentPage.Content>
<StackLayout x:Name="ExampleElement">
<Label Text="Thanks for helping"/>
</StackLayout>
<!-- various elements -->
</ContentPage.Content>
</ContentPage>
I have a simple page with text block and a button. I want to change the text when I press the button. But using a text from a Data.Name property of the Page.
I know I can have this simpler (having just Name instead of Data.Name), but I need Data.Name, don't ask why.
For this I have a class DataType which has the Name property and object named Data of that class. I want to have Data inside this Page, and bind the text to the Data.Name property.
When I click on the button, nothing happens, the question is how canI make this work?
XAML:
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding Data.Name, Mode=OneWay}"/>
<Button Grid.Row="1" Content="Change" Click="Button_Click"/>
</Grid>
</Page>
Class DataType
public ref class DataType: public INotifyPropertyChanged {
public:
property String^ Name
{
String^ get() {
return m_Name;
}
void set(String^ value) {
m_Name = value;
OnPropertyChanged("Name");
}
}
virtual event PropertyChangedEventHandler^ PropertyChanged;
private:
void OnPropertyChanged(Platform::String^ propertyName)
{
PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}
String^ m_Name;
};
Class MainPage
public ref class MainPage sealed: public INotifyPropertyChanged
{
public:
MainPage();
property DataType^ Data {
DataType^ get() {
return m_Data;
}
void set(DataType^ value) {
m_Data = value;
OnPropertyChanged("Data");
}
}
virtual event PropertyChangedEventHandler^ PropertyChanged;
private:
void OnPropertyChanged(Platform::String^ propertyName)
{
PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}
void Button_Click(Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e)
{
Data->Name = rand() & 1 ? "Test1" : "Test2";
OnPropertyChanged("Data");
}
DataType^ m_Data;
};
In UWP, there are x:Bind and Binding markup extension, they have some differences when you use them. You can learn the details from the document of above two links.
Now we will discuss the reason that caused your above issue.
In your xaml, you use the Binding markup extension to bind the property path, since Binding uses the DataContext as a default source. Simplely to say, when you use Binding property path, you bind the DataContext.Property path, you just need use the Bind source object's property but not need to specify the Source data object on the xaml. As the introduction of Traversing an object graph:
"{Binding Path=Customer.Address.StreetAddress1}"
Here's how this path is evaluated:
The data context object (or a Source specified by the same Binding) is searched for a property named "Customer".
The object that is the value of the "Customer" property is searched for a property named "Address".
The object that is the value of the "Address" property is searched for a property named "StreetAddress1".
See the Property-path syntax for the details.
So your code will work just binding the Name property and set the DataContext. (Note that: your MainPage class don't need to implement the INotifyPropertyChanged interface.)
<TextBlock Grid.Row="0" Text="{Binding Name, Mode=OneWay}"/
And
this->DataContext = Data;
Also note: If you're using Visual C++ component extensions (C++/CX) then, because we'll be using {Binding}, you'll need to add the BindableAttribute attribute to the DataType class.
[Windows::UI::Xaml::Data::Bindable]
public ref class DataType sealed : public INotifyPropertyChanged {
...
}
On the other hand, you can use the x:Bind instead of the Binding, since x:Bind don't use the DataContext as a default source—instead, it uses the page or user control itself. So it will look in the code-behind of your page or user control for properties, fields, and methods. To expose your view model to {x:Bind}, you will typically want to add new fields or properties to the code behind for your page or user control. For example: in a page, Text="{x:Bind Employee.FirstName}" will look for an Employee member on the page and then a FirstName member on the object returned by Employee.
The issue is that DataType properties isn't visible from XAML, because "DataType.h" isn't included in "pch.h"
Once I included it in precompiled header, everything worked.
BTW, looks like Binding is not checking for type visibility from XAML or whatever, but x:Bind does. Using x:Bind, the compiler complains about unknown Data.Name, and that allowed me to figure out the problem.
I have a IValueConverter that has a System.Type property which is set in XAML.
Converter:
internal class EnumTypeConverter : IValueConverter
{
public Type TypeToDisplay { get; set; }
public object Convert(object value, Type targetType, object parameter, string language)
{
return TypeToDisplay?.FullName;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
XAML:
<Page
x:Class="UWPSystemTypeConverterTest.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converter="using:UWPSystemTypeConverterTest.Converter"
xmlns:enums="using:UWPSystemTypeConverterTest.Enum"
mc:Ignorable="d">
<Page.Resources>
<converter:EnumTypeConverter x:Key="Converter" TypeToDisplay="enums:CustomEnum" />
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="{Binding Converter={StaticResource Converter}}" />
</Grid>
</Page>
When I run the application, I get following error:
Windows.UI.Xaml.Markup.XamlParseException: 'The text associated with
this error code could not be found.
Failed to create a
'UWPSystemTypeConverterTest.Converter.EnumTypeConverter' from the text
'enums:CustomEnum'. [Line: 14 Position: 56]'
If I add a property of type CustomEnum to the code- behind file, which is never used, the application works.
the changed code- behind- File:
public sealed partial class MainPage : Page
{
public CustomEnum WithThisPropertyTheAppWorks { get; set; }
public MainPage()
{
InitializeComponent();
this.DataContext = this;
}
}
The complete project for reproduction is here: https://github.com/SabotageAndi/UWPSystemTypeConverterTest
Line to uncomment is https://github.com/SabotageAndi/UWPSystemTypeConverterTest/blob/master/UWPSystemTypeConverterTest/MainPage.xaml.cs#L13
I suspect that an optimiser of UWP is causing this problem.
Is this really the case?
How can I fix the error without the unused property in the code-behind file?
Targeting UWP Build 10240, a viable work around is to add a dummy instance of the targeted enum in static resources of the page before instantiating the converter.
<Page.Resources>
<enums:CustomEnum x:Key="WorkAround">CustomEnumValue</enums:CustomEnum>
<converter:EnumTypeConverter x:Key="Converter" TypeToDisplay="enums:CustomEnum" />
</Page.Resources>
Info from a MSFT employee on a MVP mailing list:
This behaviour is a current limitation of UWP.
The XAML compiler and the runtime don't support System.Type- typed properties. So the needed metadata is not generated and the runtime can not convert the string to the type.
But because of the public properties on the code-behind, the compiler generates the needed metadata now. I am not that happy with the work around, but it is better than other solutions (e.g. a string property with the fullname to the type).
I'm trying to access a static resource's property:
<ResourceDictionary>
<FeatureControl
x:Key="FeatureControl"
IsSweetFeatureEnabled="True">
<SweetFeature IsEnabled="{StaticResource FeatureControl.IsSweetFeatureEnabled}"/>
</ResourceDictionary>
But this gives me a runtime error.
All the posts I've found are dealing with wpf and not uwp.
I know I can pass FeatureControl and access the property from within the SweetFeature class, but the SweetFeature class does not need know about what other features are enabled.
Any ideas?
Edit
This is how the property is defined:
public class SweetFeature
{
public bool IsEnabled { private get; set; }
...
}
This should work for the binding:
IsEnabled="{Binding IsSweetFeatureEnabled, Source={StaticResource FeatureControl}}"