I have built a good working WPF-application which is mvvm compliant. I have one window, in which I am controlling via a property(public Object named CurrentHomeView) which Datatemplate will be shown. For example: If my property equals LoginViewModel, the window will display the LoginControl which is a UserControl. So far so good.
Now I want to take this system and use it in a xamarin app. But the way I made it in my WPF-application seems not to be the right way in xamarin.
What do I have to do, that it will work the same way?
You will the in my code, what I have built so far. My ContantPage is not changing the constant.
My Class to manage the value of my property:
namespace Core.ApplicationStartUp
{
public class StartUp : BindableBase
{
LoginViewModel _loginViewModel;
private bool _isNewUser;
private object _currentMainView;
public object CurrentMainView
{
get => _currentMainView;
set => SetProperty(ref _currentMainView, value);
}
public bool IsNewUser
{
get => _isNewUser;
set => SetProperty(ref _isNewUser, value);
}
public StartUp()
{
_isNewUser = SearchUserData();
if (!IsNewUser)
LoadLoginControl();
}
private bool SearchUserData()
{
return false;
}
private void LoadLoginControl()
{
_loginViewModel = new LoginViewModel(this);
bool isIn = _loginViewModel.CheckStatus();
if (!isIn)
CurrentMainView = _loginViewModel;
else
_loginViewModel.Login();
}
}
}
`
My Window in the WPF application:
<Window.Resources>
<DataTemplate DataType="{x:Type loginCore:LoginViewModel}">
<loginControl:LoginControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type registrationCore:RegistrationViewModel}">
<registrationControl:RegistrationControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type navigationCore:NavigationViewModel}">
<navigationControl:NavigationControl/>
</DataTemplate>
</Window.Resources>
<Border BorderThickness="0.5" BorderBrush="White">
<DockPanel LastChildFill="True">
<ContentControl Content="{Binding CurrentMainView, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
</DockPanel>
</Border>
And this is my Page in Xamarin, which should use the same Class to mage the active View:
<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"
xmlns:navigationControl="clr-namespace:Mobile.Areas.Navigation"
xmlns:navigationCore="clr-namespace:Core.Areas.Navigation;assembly=Core"
xmlns:registrationCore="clr-namespace:Core.Areas.Registration;assembly=Core"
xmlns:registrationControl="clr-namespace:Mobile.Areas.Registration"
xmlns:loginCore="clr-namespace:Core.Areas.Login;assembly=Core"
xmlns:loginControl="clr-namespace:Mobile.Areas.Login"
x:Class="Mobile.MainWindow">
<ContentPage.Resources>
<DataTemplate x:DataType="{x:Type loginCore:LoginViewModel}"
x:Key="LoginControl">
<loginControl:LoginControl/>
</DataTemplate>
<DataTemplate x:DataType="{x:Type registrationCore:RegistrationViewModel}"
x:Key="RegistrationControl">
<registrationControl:RegistrationControl/>
</DataTemplate>
<DataTemplate x:DataType="{x:Type navigationCore:NavigationViewModel}"
x:Key="NavigationControl">
<navigationControl:NavigationControl/>
</DataTemplate>
</ContentPage.Resources>
<StackLayout>
<ContentView Content="{Binding CurrentMainView}"/>
</StackLayout>
</ContentPage>
I do not get any exceptions or errors. Further more there is no information in the output.
There is no equivalent to a WPF ContentControl in Xamarin.Forms. You could create a custom ContentView as suggested here:
public class ContentPresenter : ContentView
{
public static readonly BindableProperty ItemTemplateProperty = BindableProperty.Create("ItemTemplate", typeof(DataTemplate), typeof(ContentPresenter), null, propertyChanged: OnItemTemplateChanged);
private static void OnItemTemplateChanged(BindableObject bindable, object oldvalue, object newvalue)
{
var cp = (ContentPresenter)bindable;
var template = cp.ItemTemplate;
if (template != null)
{
var content = (View)template.CreateContent();
cp.Content = content;
}
else
{
cp.Content = null;
}
}
public DataTemplate ItemTemplate
{
get
{
return (DataTemplate)GetValue(ItemTemplateProperty);
}
set
{
SetValue(ItemTemplateProperty, value);
}
}
}
There is also no support for implicit data templates. You can work around this by using a converter as suggested here.
As I understand, you want to reuse your ViewModels from WPF app in Xamarin app - this is doable, but not easy.
The way you did view switching in WPF works and is OK, but it is not "how it's done" when you want to go multiplatform.
First thing you should do, is to make sure that ViewModels are platform agnostic - put them all in a PCL. Proper MVVM app should be able to run and do stuff even without any views attached - this includes switching the active ViewModel.
You achieve this by using Navigation/Routing service - most MVVM frameworks have that. Basically, it is a thing that you ask to show some ViewModel - if you want to learn a lot, write your own, otherwise use of the existing, it's not worth your time to invent it one more time. How the VM is shown depends on what platform the program is running - there should be no difference from VM point of view though. Still, you will need seperate Views (UserControls in WPF, Pages in Forms) for each platform.
One of the best MVVM frameworks in my opinion is ReactiveUI, written with Xamarin in mind, so it's fast, has low startup time etc. It also works with most platforms: WPF, WinForms, Windows Universal Apps, even Avalonia and some others. RxUI even supports views activation out of the box which is more than handy. Also, once you go reactive, there is no way back :)
This article conveys the basics of the routing concept.
Happy digging from there :)
Related
So I'm missing something simple or losing my mind. I am trying to reuse a class for multiple pages in a WPF application and bind the properties to the pages that instance it. I've tried setting the DataContext but I'm missing something. I'm loading the StockAnalysis page and then creating instance of the PriceChart class (this is the class for reuse) and I want the properties set in the PriceChart class to be the data to bind to the Stock.xaml.cs page. Even in setting the DataContext it is still looking for the StockAnalysis object. Why?
Stock.xaml.cs
public partial class StockAnalysis : Page
{
PriceChart PChart = new PriceChart();
public StockAnalysis()
{
InitializeComponent();
//Load The Data
List<Stock> HistoricalPrice = Database.GetPrices(ticker);
//Create The Charts
this.DataContext = PChart;
PChart.ShowPriceChart(HistoricalPrice);
}
}
Stock.xaml (Look at the Last TexBlock for the Binding of "LastPrice")
<Page x:Class="Stock.StockAnalysis"
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"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:local="clr-namespace:Stock"
mc:Ignorable="d"
d:DesignHeight="1000" d:DesignWidth="1200"
Title="Stock Analysis">
<StackPanel x:Name="LastClosePanel" Grid.Row="0" Grid.RowSpan="2" Grid.Column="5" Height="60" VerticalAlignment="Top" Margin="1,0,0,1" Style="{StaticResource LastCloseBackground}">
<TextBlock x:Name="LastCloseText" Foreground="OrangeRed" FontSize="12" HorizontalAlignment="Center" Margin="0,10,0,8">Last Close</TextBlock>
<TextBlock x:Name="LastCloseBind" Foreground="White" FontSize="16" HorizontalAlignment="Center" Text="{Binding LastPrice}"></TextBlock>
</StackPanel>
</Page>
PriceChart.cs (This is where I assign "LastPrice" in hopes to bind it to the TextBlock in stock.xaml.cs)
public class PriceChart
{
public string LastPrice { get; set; }
public void ShowPriceChart(List<Stock> FullList)
{
LastPrice = FullList[0].LastPrice.ToString("C");
//DO OTHER THINGS
}
}
The problem is that PriceChart doesn't implement any change notification. With the current code, this is how things will go when StockAnalysis gets created:
InitializeComponent() will create the TextBlocks and the binding. At this point, DataContext is null, so the binding will fail and the TextBlock stay empty.
this.DataContext = PChart will trigger a binding update (because DataContext is a DependencyProperty, which means it does support change notification). When the binding updates, it will pull the value of LastPrice, which is currently still empty.
ShowPriceChart will set the value of LastPrice, but because PriceChart doesn't support change notification, the binding doesn't know it needs to update, so the TextBlock stays empty.
To solve this, I would recomend your PriceChart implement the INotifyPropertyChanged interface per this article: How to: Implement Property Change Notification.
(Technically, moving PChart.ShowPriceChart(HistoricalPrice) before this.DataContext = PChart would also "solve" the problem, but only if you never need to update the bindings again after initialization.)
Im new to MVVM and try to follow all the guidelines I find to respect it. I would like to have a Busy-Animation on one of my usercontrols. I want to include it on the control like this.
The Usercontrol it is nested in is shown on the MainWindow using a DataTemplate for a ViewModel, for example like so:
<Window.Resources>
<DataTemplate DataType="{x:Type AppViews:AppConfigViewModel}">
<local:AppConfigView />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</Grid>
When running this, the Application is shown and I also see the view for the AppConfigViewModel which is bind correctly since underlying values are displayed correctly in the view.
Now I tried to register the Busy-Animation in the ViewModel (to control it from there) by doing this in the Constructor of the BusyAnimation:
(DataContext as PageViewModel).BusyAnim = this;
For some reason the DataContext is always null and the result of this line is an exception. What am I doing wrong here?
What I tried to did there is against the idea of MVVM.
I tried downcasting an object that is meant to be general.
A better aproach for the task I tried to achieve is implementing dependency properties in the busy animation component. Those are meant to be bound to from the viewmodel of the mainly displayed view. that way the busy animation can be shown when some property in the viewmodel changes. That could be for example a bool with the name "working".
this is the dependency property code in my busy animation:
public static readonly DependencyProperty ShowBusyProperty = DependencyProperty.Register("ShowBusy", typeof(Boolean), typeof(FortschrittView), new PropertyMetadata(false, OnShowBusyPropertyChanged));
public Boolean ShowBusy
{
get { return (Boolean)this.GetValue(ShowBusyProperty); }
set { this.SetValue(ShowBusyProperty, value); }
}
private static void OnShowBusyPropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
FortschrittView myUserControl = dependencyObject as FortschrittView;
myUserControl.OnPropertyChanged("ShowBusy");
myUserControl.OnShowBusyPropertyChanged(e);
}
private void OnShowBusyPropertyChanged(DependencyPropertyChangedEventArgs e)
{
if(ShowBusy)
{
Start();
}
else
{
Stop();
}
}
Yes its a lot of code, but I feel wpf wants it that way. Remember above code is in the busy-animation user control and triggers Start() Stop() functions which control storyboards.
Below xaml is in the control that uses the busyanimation, binding it to a viewmodel that the busy-animation should indicate background-work for:
<local:BusyAnimation ShowBusy="{Binding Model.IsBusy}"/>
That ShowBusy Property there is the Dependency Property implemented above. Of course IsBusy from the model should follow the observable pattern for everything to work.
/ps: I throughoutly documented the mistakes i did and how i solved them. Can I get rid of the negative points I got somehow for creating this question?
I have two DataTemplates that gets switched depending on the current ViewModel. However whenever I switch my ViewModel, it seems to call the respective View's constructor and calls the InitializeComponent() call within the constructor, which means that whenever I switch the DataTemplate, it generates a new view that is bound to the respective DataTemplate. I am not sure why this is happening but is there a way to prevent the creation of a new View when switching ViewModels?
Below is the DataTemplates located at my MainView.
<Window.Resources>
<DataTemplate DataType="{x:Type viewModels:FirstPanelViewModel}">
<views:FirstPanelView />
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:SecondPanelViewModel}">
<views:SecondPanelView />
</DataTemplate>
</Window.Resources>
The template is being displayed in a ContentControl.
<ContentControl Grid.Row="1" Content="{Binding CurrentViewModel}" />
This is my SecondPanelView which is the same as my FirstPanelView, it's very simple.
public partial class FirstPanelView
{
public FirstPanelView()
{
InitializeComponent();
}
}
public partial class SecondPanelView
{
public SecondPanelView()
{
InitializeComponent();
}
}
My Ioc makes sure that I generate only one instance of the SecondPanelView
container.Register<IFirstPanelViewModel, FirstPanelViewModel>(new PerContainerLifetime())
container.Register<ISecondPanelViewModel, SecondPanelViewModel>(new PerContainerLifetime());
DataContext is being bounded in each view by a custom markup extension.
DataContext="{Binding Source={common:Locate}, Path=FirstPanelViewModel}"
DataContext="{Binding Source={common:Locate}, Path=SecondPanelViewModel}"
Which is just calling GetInstance of the respective ViewModel.
public IFirstViewModel FirstViewModel
{
get { return _container.GetInstance<IFirstPanelViewModel>(); }
}
public ISecondViewModel SecondViewModel
{
get { return _container.GetInstance<ISecondPanelViewModel>(); }
}
This is an old issue, but I was also struggling with this issue. The answer is to place the view instances directly in the resources and bind them to content controls in the data templates. If you do so, the view is instantiated only once.
<Window.Resources>
<views:FirstPanelView x:Key="FirstPanelViewKey"/>
<views:SecondPanelView x:Key="SecondPanelViewKey"/>
<DataTemplate x:Key="DT1">
<ContentControl Content="{StaticResource FirstPanelViewKey}" />
</DataTemplate>
<DataTemplate x:Key="DT2">
<ContentControl Content="{StaticResource SecondPanelViewKey}" />
</DataTemplate>
</Window.Resources>
I wasn't able to solve my problem even after extending ContentControl. The issue I ran into using that approach is that ContentControl's dependency property was not directly interfacable/overridable which forced me to hack on the existing dependency property. Also the intialization of a DataTemplate seems to fall deeper than the simple ContentControl.
So I decided to change the way that my views are being displayed by simply toggling their visibility. This approach worked for me since I essentially want my views to stay in the background doing its own thing and ready to be interfaced at its previous state in any moment.
The question is not about how to get the stuff working, it already does; it's about some strange behavior I'm experiencing, and I need to understand it. I have a ResourceDictionary that contains some styles, one of them got TargetType="{x:Type UserControl}" and x:Key="UCStyle"; that one is applied on multiple UserControls in the project.
Some of these UserControls got string State property in their ViewModel to be used to apply Visual States (through an external class, and an attached property, bound to the ViewModel in XAML). Till this point everything was perfect, then, I tried to add DependencyProperty State to the UserControl, and simply bind it to the state property in the ViewModel, my attempt was:
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<!--ResourceDictionary Source="..."/-->
</ResourceDictionary.MergedDictionaries>
<Style x:Key="MyStyle" TargetType="{x:Type local:MyUserControl}" BasedOn="{StaticResource UCStyle}">
<Setter Property="State" Value="{Binding State, Mode=TwoWay}"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Style>
<DynamicResourceExtension ResourceKey="MyStyle" />
</UserControl.Style>
This worked perfectly at the runtime, but in the design-time, it always underline these lines
And shows an error says:
'MyUserControl' TargetType doesn't match type of element 'UserControl'.
And doesn't apply neither UCStyle nor MyStyle in the XAML Viewer in Visual Studio, and doesn't even draw the child UserControls properly. I didn't expect the solution to run properly, but it did!
Now my questions are:
Why does it show these errors in the design-time while it runs properly?
How to get rid of these errors in the design-time? (I cleaned, and re-built the solution, and restarted Visual Studio, and none of these worked)
What's the best practice to deal with `UserControl` Visual States in such situation in MVVM?
What's the best practice to bind a DependencyProperty of a UserControl to a property in it's ViewModel in MVVM?
I'm using Visual Studio 2012.
The wpf designer is nefarious for showing bogus errors at design time. You can't do much but ignore them.
Visual states are a concern of the UI, and therefore should be contained within the UI. MVVM does not mean no codebehind. Use your codebehind for UI tasks, and put your business logic in your view models.
Your question suggests you're creating custom view models to hold view logic for your user controls. Seriously, don't do that. That'll get you in trouble down the road. It interferes with how databinding is designed to work.
There is no "best practice" for binding user control elements to properties defined on its surface. It depends. Using a style to do this seems odd, however. You can simply give the root of the UserControl an x:Name="root" and then use ElementName=root in your binding.
An example of binding within a UserControl to a property defined on the UserControl (taken from an old prototype)...
Here's a UserControl designed to add or delete a list of stuff.
DependencyProperties defined on the UserControl
Bindings within the UserControl that bind to these properties
I don't guarantee this works, but it will illustrate how it's done:
public partial class ItemsEditor : UserControl
{
#region Items
public static readonly DependencyProperty ItemsProperty =
DependencyProperty.Register(
"Items",
typeof(IEnumerable<Item>),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public IEnumerable<Item> Items
{
get { return (IEnumerable<Item>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
#endregion
#region AddItem
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register(
"AddItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand AddItem
{
get { return (ICommand)GetValue(AddItemProperty); }
set { SetValue(AddItemProperty, value); }
}
#endregion
#region RemoveItem
public static readonly DependencyProperty RemoveItemProperty =
DependencyProperty.Register(
"RemoveItem",
typeof(ICommand),
typeof(ItemsEditor),
new UIPropertyMetadata(null));
public ICommand RemoveItem
{
get { return (ICommand)GetValue(RemoveItemProperty); }
set { SetValue(RemoveItemProperty, value); }
}
#endregion
public ItemsEditor()
{
InitializeComponent();
}
}
It just lists a bunch of things, you can add a new thing or delete a thing from the list. Here's the bindings in xaml
<UserControl x:Class="LolPrototype.ItemsEditor"
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"
xmlns:t="clr-namespace:UCsAndICommands"
x:Name="root">
<UserControl.Resources>
<DataTemplate DataType="{x:Type t:Item}">
<StackPanel Orientation="Horizontal">
<Button Command="{Binding RemoveItem, ElementName=root}"
CommandParameter="{Binding}">Remove</Button>
<TextBox Text="{Binding Name}" Width="100"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<StackPanel>
<Button Command="{Binding AddItem, ElementName=root}">Add</Button>
<ItemsControl ItemsSource="{Binding Items, ElementName=root}" />
</StackPanel>
</UserControl>
Obviously, you can define DataTemplates outside the list in an ancestor's resources. The point is to show how ElementName bindings can be used to bind against properties defined in the UserControl.
EDIT:
The problem was that my View Models were not public, so the bindings were not found. For some reason they need to be public in Silverlight, even though private view models work for WPF. More detail in my answer below.
I am trying to implement an MVVM WPF app in Silverlight, but I have never used Silverlight before. I am struggling to get the bound content to display. I am implementing it almost exactly how I did with WPF, except that the Main bit is a UserControl, not a Window, the app is hosted by a Web Site, and I had to remove the x:Type in DataTemplate DataType
My Website-to-MainPage is working properly, because I can display a text box or something in that page, however the Binding is just not displayed. Any suggestions?
My MainPage.xaml.cs:
public partial class MainPage : UserControl
{
private MainPageViewModel _viewModel;
public MainPage()
{
InitializeComponent();
_viewModel = new MainPageViewModel();
this.DataContext = _viewModel;
}
}
My MainPage.xaml:
<UserControl x:Class="TransformationServices.Silverlight.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"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400"
xmlns:view="clr-namespace:TransformationServices.Silverlight.View"
xmlns:viewModel="clr-namespace:TransformationServices.Silverlight.ViewModel"
xmlns:local="clr-namespace:TransformationServices.Silverlight"
Background="#FF2D2D30">
<UserControl.Resources>
<local:ViewConverter x:Key="viewConverter"/>
<DataTemplate DataType="viewModel:InputSelectViewModel">
<TextBlock Text="Hello World!"/>
<!-- This textblock is just for testing. It doesn't work, and neither does the following line-->
<view:InputSelect/>
</DataTemplate>
</UserControl.Resources>
<ContentControl Content="{Binding CurrentView}" />
</UserControl>
My MainPageViewModel.cs
class MainPageViewModel : ViewModelBase, INotifyPropertyChanged
{
private ViewModelBase _currentView;
private List<ViewModelBase> _viewModels;
private int _viewIndex;
public ViewModelBase CurrentView
{
get { return _currentView; }
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
public MainPageViewModel()
{
_viewModels = new List<ViewModelBase>();
// Add view models here
_viewModels.Add(new InputSelectViewModel());
_viewIndex = 0;
CurrentView = _viewModels[_viewIndex];
}
}
It would appear as though the problem is that my ViewModels were not public. I noticed in the Output the following type of message:
System.Windows.Data Error: Cannot get '<Whatever property of View Model>' value ...
This message was coming up for every property that I was binding to, including CurrentView. After making the View Models public, the bindings worked perfectly. It seems odd to me that Silverlight would need the classes to be public when they worked fine as private for WPF, but this is my first time working with Silverlight, so this intricacy tripped me up. Hopefully this answer can help someone in the future. Anyway, this is the main reason for why the bindings were not working, and thus the CurrentView was not being displayed.
Another reason that it was hard for me to debug this issue is that my page was accidentally caching everything, so some of my changes would not display. This link helped me with that:
http://www.codeproject.com/Articles/143414/Prevent-your-Silverlight-XAP-File-from-Caching-in