Mvvm Light, UWP and code behind - c#

Base on this excellent presentation from Laurent Bugnion at Xamarin Evolve 2014, I'm trying to create my first UWP/MVVM Light application.
I created a very simple Article : ObservableObject class with 2 string properties : Référence and Désignation.
In the view model associated to the article list view, I have an action to create a new article :
public ArticlesViewModel(IArticleService dataService, INavigationService navigationService)
{
ArticleService = dataService;
NavigationService = navigationService;
CréeArticleCommand = new RelayCommand(CréeArticle);
}
public RelayCommand CréeArticleCommand { get; private set; }
private void CréeArticle()
{
if (!CréeArticleCommand.CanExecute(null))
return;
NavigationService.NavigateTo(ViewModelLocator.ArticleDetail_Key,
new ArticleViewModel(new Article(),
ArticleService,
NavigationService));
}
here is the XAML for my Article detail view :
<!-- language: xaml -->
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UniversalTest1.UWP.Articles"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Editors="using:DevExpress.UI.Xaml.Editors"
x:Class="UniversalTest1.UWP.Articles.Article_Detail"
mc:Ignorable="d"
xmlns:vm="clr-namespace:UniversalTest1.Data.ViewModels.Articles;assembly=UniversalTest1.Data"
d:DataContext="{d:DesignInstance Type=vm:ArticleViewModel, IsDesignTimeCreatable=True}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBlock Text="Référence :" HorizontalAlignment="Left" Margin="24,15,0,0" VerticalAlignment="Top"/>
<TextBlock Text="Désignation :" HorizontalAlignment="Left" Margin="10,52,0,0" VerticalAlignment="Top"/>
<Editors:TextEdit Text="{Binding Article.Référence, Mode=TwoWay}" HorizontalAlignment="Left" Margin="100,8,0,0" VerticalAlignment="Top" Width="300"/>
<Editors:TextEdit Text="{Binding Article.Désignation, Mode=TwoWay}" HorizontalAlignment="Left" Margin="100,45,0,0" VerticalAlignment="Top" Width="500"/>
<Button Content="Sauver" Command="{Binding SauverCommand}" HorizontalAlignment="Left" Margin="102,84,0,0" VerticalAlignment="Top"/>
</Grid>
</Page>
My problem here is that I have to define the DataContext in the code behind of my page :
public sealed partial class Article_Detail : Page
{
public Article_Detail()
{
this.InitializeComponent();
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
DataContext = (ArticleViewModel)e.Parameter;
}
}
Is there a way to keep the design time DataContext as defined in the d:DataContext part of the Xaml's Page, and at runtime, get the DataContext from the Navigation parameter ?
My goal here is to have the less amount possible of code in the code behind. So I would like to define the runtime DataContext in the XAML also.

You can make use of dependency injection to create design or runtime service instances for your viewmodel. Using a view model locator you can do something like this:
public class ViewModelLocator
{
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
if (!SimpleIoc.Default.IsRegistered<IArticleService>())
{
SimpleIoc.Default.Register<IArticleService, DesignArticleService>();
}
}
else
{
if (!SimpleIoc.Default.IsRegistered<IArticleService>())
{
SimpleIoc.Default.Register<IArticleService, ArticleService>();
}
}
SimpleIoc.Default.Register<ArticleViewModel>();
}
public ArticleViewModel ArticleViewModel => ServiceLocator.Current.GetInstance<ArticleViewModel>();
}
And in your App.xaml you register the locator
<Application
x:Class="UniversalTest1.App" // your namespace
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"
mc:Ignorable="d"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModel="using:UniversalTest1.Data.ViewModels"> // your namespace
<Application.Resources>
<ResourceDictionary>
<viewModel:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
</ResourceDictionary>
</Application.Resources>
</Application>
And then you can reference it in your xaml like this:
<Page
...
DataContext="{Binding ArticleViewModel, Source={StaticResource Locator}}">
You could also take a look at the sample code here https://mvvmlight.codeplex.com/SourceControl/latest#Samples/Flowers/Flowers.Data/ViewModel/ViewModelLocator.cs

For this, you need to use your own implementation of NavigationService. The concept is to navigate to your page and call your ViewModel at the same time to handle parameters and set the DataContext.
Here are two samples of this pattern:
Prism: https://github.com/PrismLibrary/Prism/blob/master/Source/Windows10/Prism.Windows/Navigation/FrameNavigationService.cs
Template10: https://github.com/Windows-XAML/Template10/blob/master/Template10%20(Library)/Services/NavigationService/NavigationService.cs

Related

How to set datacontext for the Datagrid

I 'm learning wpf through mvvm. I have a ViewModel which is binded to the view through the window datacontext
Window.Xaml:
xmlns:Converter="clr-namespace:UploadFileToDB.ViewModel"
<Window.DataContext>
<Converter:ViewModel/>
</Window.DataContext>
The application works fine when all the methods are present inside the ViewModel Class.
Now I have a datagrid in the view, for populating the datagrid I have created a seperate class called populatedatagrid under the ViewModel namespace.
pseudocode which is not working :
namespace UploadFileToDB.ViewModel
{
public class PopulateDatagrid
{
public ObservableCollection<datagridmodel> modelclasswithcombobox
{
get;
set;
}
DataSet ds;
public void comboboxdata()
{ //This code populate the observable collection}
}
}
public class ViewModel : INotifyPropertyChanged
{
PopulateDatagrid populatedatagridwithobservablecollection = new PopulateDatagrid();
public ViewModel()
{
populatedatagridwithobservablecollection.combobxdata();
//Calling the above class method here to populate data. But this throws the error that BindingExpression path error that ModelClassWithcombobox property not found on object ViewModel
}
}
}
The above code is not working correctly due to the binding expression error.
pseudocode which is working :
namespace UploadFileToDB.ViewModel
{
public class ViewModel : INotifyPropertyChanged
{
public ObservableCollection<datagridmodel> modelclasswithcombobox
{
get;
set;
}
DataSet ds;
public void comboboxdata()
{ //This code populate the observable collection}
}
public ViewModel()
{
comboboxdata();
}
}
}
The above code works perfectly without any issue. On my understanding since I have mapped only ViewModel class on datacontext. The xaml can't able to find the observable collection which is present in the other class.
Can anyone help me on how to acheive segreagration of class and provide solution to above scenario
Edit : As requested window.xaml
<Window
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="UploadFileToDB.Window1"
xmlns:vm ="clr-namespace:UploadFileToDB.ViewModel"
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:Converter="clr-namespace:UploadFileToDB.ViewModel"
Title="Window1" Height="400" Width="800">
<Window.DataContext>
<Converter:ViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1500*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="41*"/>
</Grid.RowDefinitions>
<Tabcontrol>
<TabItem Header="Re Assign">
<StackPanel>
<DataGrid Height="150" x:Name="datagrid1" IsReadOnly="True" AutoGenerateColumns="True" CanUserAddRows="False" Width="507" Margin="150,-35,20,79" HorizontalAlignment="Left">
<DataGrid.Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="ItemsSource" Value="{Binding modelclasswithcombobox}"/>
</Style>
</DataGrid.Style>
</DataGrid>
</StackPanel>
</TabItem>
</TabControl>
</Grid>
</Window>
You can only bind to the properties, not to the fields. So for it works you should use something like this:
public class ViewModel : INotifyPropertyChanged
{
public PopulateDatagrid Populatedatagridwithobservablecollection
{
get
{
return _populatedatagridwithobservablecollection;
}
set
{
if (value != _populatedatagridwithobservablecollection)
{
_populatedatagridwithobservablecollection = value;
NotifyPropertyChanged(nameof(Populatedatagridwithobservablecollection));
}
}
}
private PopulateDatagrid _populatedatagridwithobservablecollection = new PopulateDatagrid();;
}

Null reference error in App.xaml MVVM light

I'll make a WPF application whit one window and by changing the content of the Frame I'll navigate troth my application. For this I'm using MVVM light.
But on App.xaml I've got this error in the error list of Visual Studio.
Object reference not set to an instance of an object.
Here is the code where the error happens:
<Application
x:Class="Project.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Project"
StartupUri="MainWindow.xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d1p1:Ignorable="d"
xmlns:vm="clr-namespace:Project.ViewModel"
xmlns:services="clr-namespace:Project.Services"
xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<Application.Resources>
<ResourceDictionary>
<services:IocContainer x:Key="ioc" />
<vm:ApplicationViewModel x:Key="appvm" d:IsDataSource="True" /> <!-- error happens on this line -->
</ResourceDictionary>
</Application.Resources>
</Application>
This is my MainWindow:
<Window x:Class="Project.MainWindow"
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:local="clr-namespace:Project"
mc:Ignorable="d"
DataContext="{StaticResource appvm}"
Title="Project" Height="450" Width="800">
<Frame>
<Frame.Content>
<Page Content="{Binding CurrentPage, Mode=TwoWay}" />
</Frame.Content>
</Frame>
</Window>
Here is my ApplicationViewModel that inherits from ViewModelBase:
public class ApplicationViewModel : ViewModelBase
{
private Page _currentPage = IocContainer.Ioc.StartScreenPage;
public Page CurrentPage
{
get
{
return _currentPage;
}
set
{
if (_currentPage != value)
{
_currentPage = value;
OnPropertyChanged();
}
}
}
public StartScreenViewModel StartScreenViewModel
{
get
{
return (App.Current.Resources["ioc"] as IocContainer)?.StartScreenViewModel;
}
}
public void Navigate(Type sourcePageType)
{
}
}
Here is the ViewModelBase that implements INotifyPropertyChanged.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
Debug.WriteLine("PropertyChanged is niet null ☺");
}
else
{
Debug.WriteLine("PropertyChanged is null");
}
}
}
Here is my IoC container:
public class IocContainer
{
static IocContainer()
{
SimpleIoc.Default.Register<ApplicationViewModel>(false);
SimpleIoc.Default.Register<StartScreenViewModel>(false);
SimpleIoc.Default.Register<StartScreenPage>(false);
}
public static IocContainer Ioc
{
get { return App.Current.Resources["ioc"] as IocContainer; }
}
public ApplicationViewModel ApplicationViewModel
{
get { return SimpleIoc.Default.GetInstance<ApplicationViewModel>(); }
}
public StartScreenPage StartScreenPage
{
get { return SimpleIoc.Default.GetInstance<StartScreenPage>(); }
}
public StartScreenViewModel StartScreenViewModel
{
get { return SimpleIoc.Default.GetInstance<StartScreenViewModel>(); }
}
}
Here is my StartScreenPage:
<Page x:Class="Project.StartScreenPage"
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:local="clr-namespace:Project"
mc:Ignorable="d"
DataContext="{Binding StartScreenViewModel, Source={StaticResource ioc}}"
Title="StartScreen">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<Label Content="Hello world" Grid.Row="0" Grid.Column="0" />
</Grid>
</Page>
And here is StartScreenViewModel.
public class StartScreenViewModel : ViewModelBase
{ }
All the application, window and pages have a default constructor that calls InitializeComponent.
I can run my application but I see an empty window.
Did I forgot anything?
Edit: Continuing on my anwser on this question: Page can have only Frame as parent and not Window, I've changed my code of the MainWindow to this:
The code on the MainWindow must be this:
<!-- Opening Window tag with all attributes -->
<Frame Content="{Binding CurrentPage, Mode=TwoWay}" />
<!-- Closing Window tag -->
This will also show the StartScreenPage on the window.
However the null reference error is still being thrown.
There's very little null checking in your code, which is where this is happening.
The best way to find the issue is to go to the Visual Studio tool panel
Debug → Windows → Exception Settings
and fully check the row labelled 'Common Language Runtime Exceptions'. When you run the code again, you should get more information about where the null exception is happening.

WPF + Caliburn Micro + MVVM: TabItem handling

I am trying to make a popup window which contains tabcontrol using WPF, Caliburn Micro and MVVM pattern, no need to use code behind in this case. The tabcontrol contains more than 1 tabitem. After digging some threads in SO for a while I combine the found solutions and can create the popup window and fill it with tabcontrol and its tabitems (I take it from this thread).
Problem: the tabitems show content (text) from view model but show no content from view. Please take a look the code attached here.
Expected I expect to see the text "Tab Item 1" as TabItem1 header and the text "Selection One" as content in TabItem1. Right now both the header and the content of TabItems contains same text "Tab Item 1".
Am I missing something? I attach here the code. Please feel free change the code. Any hints are highly appreciated.
Sequence of code:
TabItem1, TabItem2 view and viewmodel
ITabItem
PopUp window view and viewmodel
AppBootstrapper, Shell view and viewmodel
TabItem1ViewModel (TabItem2ViewModel has same content)
public class TabItem1ViewModel : Screen, ITabItem
{
public TabItem1ViewModel() => DisplayName = "Tab Item 1";
}
Attention: in following TabItem view I use Label to show the text "Selection One", but this text doesn't appear at all. Only the display name "Tab Item 1" defined in view model appears as content of TabItem1
TabItem1View (TabItem2View has same content)
<UserControl
x:Class="CmTabControl.Views.TabItem1View"
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"
d:DesignHeight="450"
d:DesignWidth="800"
mc:Ignorable="d">
<Grid>
<TabItem x:Name="TabItem1" Header="{Binding Path=DisplayName}">
<Grid x:Name="TabItem1ContentGrid">
<Label HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Selection One" />
</Grid>
</TabItem>
</Grid>
</UserControl>
ITabItem (yes, it is only empty interface)
public interface ITabItem : IScreen
{
}
PopUpViewModel
public class PopUpViewModel : Screen
{
public PopUpViewModel()
{
TabItems.Add(new TabItem1ViewModel());
TabItems.Add(new TabItem2ViewModel());
}
private readonly BindableCollection<ITabItem> _tabItems = new BindableCollection<ITabItem>();
public BindableCollection<ITabItem> TabItems
{
get => _tabItems;
set
{
if (_tabItems == null)
{
return;
}
_tabItems.Clear();
_tabItems.AddRange(value);
NotifyOfPropertyChange(() => TabItems);
}
}
}
PopUpView
<Window
x:Class="CmTabControl.Views.PopUpView"
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:local="clr-namespace:CmTabControl.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="PopUpView"
Width="800"
Height="450"
mc:Ignorable="d">
<Grid Margin="3,8,3,3" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TabControl x:Name="TabItems" />
</Grid>
</Window>
AppBootstrapper
public class AppBootstrapper : BootstrapperBase
{
private readonly SimpleContainer _container = new SimpleContainer();
public AppBootstrapper() => Initialize();
protected override object GetInstance(Type serviceType, string key) => _container.GetInstance(serviceType, key);
protected override IEnumerable<object> GetAllInstances(Type serviceType) => _container.GetAllInstances(serviceType);
protected override void BuildUp(object instance) => _container.BuildUp(instance);
protected override void Configure()
{
base.Configure();
_container.Singleton<IWindowManager, WindowManager>();
_container.Singleton<IEventAggregator, EventAggregator>();
_container.Singleton<ShellViewModel>();
_container.PerRequest<PopUpViewModel>(); // Or Singleton if there'll only ever be one
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
base.OnStartup(sender, e);
DisplayRootViewFor<ShellViewModel>();
}
}
ShellViewModel
public class ShellViewModel : Conductor<object>.Collection.AllActive
{
private IWindowManager _windowManager;
public ShellViewModel(PopUpViewModel popUpVm)
{
DisplayName = "Shell Window";
PopUpViewModel = popUpVm;
}
public PopUpViewModel PopUpViewModel { get; set; }
public sealed override void ActivateItem(object item) => base.ActivateItem(item);
public void OpenPopUp()
{
ActivateItem(PopUpViewModel);
if (_windowManager == null) _windowManager = new WindowManager();
_windowManager.ShowDialog(PopUpViewModel, null, null);
}
public sealed override string DisplayName { get; set; }
}
ShellView
<UserControl
x:Class="CmTabControl.Views.ShellView"
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"
d:DesignHeight="300"
d:DesignWidth="300"
mc:Ignorable="d">
<Grid Width="300" Height="300"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Button x:Name="OpenPopUp" Width="100" Height="35"
Content="Open Popup" />
</Grid>
</UserControl>
Added: Screenshot of Live Visual Tree.
I found a solution that uses Templates:
PopUpViewModel add SelectedTab:
public sealed class PopUpViewModel : Screen
{
private readonly BindableCollection<ITabItem> _tabItems = new BindableCollection<ITabItem>();
private IScreen _selectedTab;
public PopUpViewModel()
{
DisplayName = "Popup";
TabItems.Add(new TabItem1ViewModel());
TabItems.Add(new TabItem2ViewModel());
SelectedTab = TabItems.FirstOrDefault();
}
public BindableCollection<ITabItem> TabItems
{
get => _tabItems;
set
{
if(_tabItems == null)
return;
_tabItems.Clear();
_tabItems.AddRange(value);
NotifyOfPropertyChange(() => TabItems);
}
}
public IScreen SelectedTab
{
get => _selectedTab;
set
{
_selectedTab = value;
NotifyOfPropertyChange();
}
}
}
PopUpView:
<Grid Margin="3, 8, 3, 3"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TabControl ItemsSource="{Binding TabItems}"
SelectedItem="{Binding SelectedTab,
UpdateSourceTrigger=PropertyChanged}">
<TabControl.ItemTemplate>
<DataTemplate>
<Label Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl cal:View.Model="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
Now you only add the TabItem content to your pages, TabItem1View:
<UserControl x:Class="WpfTestApp.Views.Tabs.TabItem1View"
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:local="clr-namespace:WpfTestApp.Views.Tabs"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid x:Name="TabItem1ContentGrid">
<Label HorizontalAlignment="Left"
VerticalAlignment="Top"
Content="Selection One" />
</Grid>
</UserControl>
Edit:
SelectedTab is just there so the first tab is selected by default.

Data Binding in UWP User Controls using Template 10

I cannot get binding to work in my app with a user control. It is a UWP app with template 10.
I use the same bind in the mainpage as I do in the user control but the field in the user control does not react to changes. I have read several articles that tell me how to set the datacontent of my user control but I can not get any of them to work.
My code is as follows:
Mainpage.xaml
Page x:Class="UserControlTest.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:controls="using:Template10.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UserControlTest.Views"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UserControlTest.ViewModels" mc:Ignorable="d">
<Page.DataContext>
<vm:MainPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<controls:PageHeader x:Name="pageHeader" Content="Main Page"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignTopWithPanel="True" />
<TextBlock x:Name="mainTextBlock" Margin="16,16,16,16"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.Below="pageHeader"
Text="{Binding TextToShow}" />
<local:ShowText Margin="16,16,16,16"
RelativePanel.Below="pageHeader"
RelativePanel.AlignRightWithPanel="True"/>
<Button Content="Change Text" Margin="16,16,16,16"
Command="{x:Bind ViewModel.ChangeText }"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.Below="mainTextBlock"/>
</RelativePanel>
</Page>
MainPageViewModel.cs
using System.Collections.Generic;
using System;
using System.Linq;
using System.Threading.Tasks;
using Windows.Security.Cryptography.Core;
using Template10.Mvvm;
using Template10.Services.NavigationService;
using Windows.UI.Xaml.Navigation;
namespace UserControlTest.ViewModels
{
public class MainPageViewModel : ViewModelBase
{
private string _textToShow = "Initial text";
public string TextToShow
{
get { return _textToShow; }
set { Set(ref _textToShow, value); }
}
DelegateCommand _changeText;
public DelegateCommand ChangeText
=> _changeText ?? (_changeText = new DelegateCommand(ExecuteChangeTest, CanChangeText));
private void ExecuteChangeTest()
{
TextToShow = "Changed text";
}
private bool CanChangeText()
{
return true;
}
}
}
ShowText.xaml
<UserControl
x:Class="UserControlTest.Views.ShowText"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UserControlTest.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:UserControlTest.ViewModels" mc:Ignorable="d">
<UserControl.DataContext>
<vm:MainPageViewModel x:Name="ViewModel" />
</UserControl.DataContext>
<Grid>
<TextBlock x:Name="UserControlTextBlock"
Text="{Binding TextToShow}" />
</Grid>
</UserControl>
At first glance the problem is that your UserControl and your MainPage are using two different instances of ViewModel. When you create your static resource
<vm:MainPageViewModel x:Name="ViewModel" />
You're creating an instance of MainPageViewModel. This means that when you set the value in from your MainPage instance of MainPageViewModel, it's not propagating to the second instance of MainPageViewModel that your UserControl created.
No worries though, we can fix this.
The DataContext of your user control should be set automatically to its parent's DataContext.
So Let's assume for a moment that you use your UserControl in your MainPage.xaml
<Page.DataContext>
<vm:MainPageViewModel x:Name="ViewModel" />
</Page.DataContext>
<MyUserControls:MyUserControl />
In this instance, MyUsercontrol is using the MainPageViewModel as it's DataContext. HOWEVER, this probably won't work, it's not a finished solution.
What you need to do is go to your UserControl's Xaml.CS and create a dependency property that you can bind to.
public class MyUserControl : Control{
public string MyControlsText
{
get { return (string)GetValue(MyControlsTextProperty); }
set { SetValue(MyControlsTextProperty, value); }
}
public static readonly DependencyProperty MyControlsTextProperty =
DependencyProperty.Register("MyControlsText", typeof(string),
typeof(MyUserControl), new PropertyMetadata(String.Empty));
}
Now that we have our dependency property, in our UserControl's Xaml we can bind to it.
<Grid>
<TextBlock x:Name="UserControlTextBlock"
Text="{Binding MyControlsText}" />
</Grid>
And finally in our MainPage.Xaml we can finalize the binding
<MyUserControls:MyUserControl MyControlsText="{Binding TextToShow}" />
So with the above code here is what we are accomplishing:
The DataContext is set in the MainPage.xaml and all of the children of MainPage.xaml share this DataContext
We created a dependency property in our UserControl that we can bind to
We bind the XAML of our UserControl to our dependency property
We Bind our view model's text property to our new user controls dependency property.

How do I make the simplest viewmodel work?

Im trying to understand how to use a viewmodel to bind data to a wpf window but cannot find any simple examples or explanations on how that is achieved.
This is what I have so far:
ViewModel.cs
public class ViewModel
{
public string Info = "Infoo";
}
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
}
MainWindow.xaml
<Window x:Class="PeopleApp.MainWindow"
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:local="clr-namespace:PeopleApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="212,137,0,0" TextWrapping="Wrap" Text="{Binding Info}" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
Why does the textbox not show "Infoo"? What am I missing?
Edit:
I've changed the field in the ViewModel to a property, but how do I connect the ViewModel to actual data?
Mistakes in your code:
You need to have property on the VM not the public field.
you need to set the value for the property of VM instance before assigning it to the DataContext of MainWindow
This should work.
MainWindow.xaml
<Window x:Class="PeopleApp.MainWindow"
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:local="clr-namespace:PeopleApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="212,137,0,0" TextWrapping="Wrap" Text="{Binding Info}" VerticalAlignment="Top" Width="120"/>
</Grid>
</Window>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel() { Info = "Infoo" };
}
}
ViewModel.cs
public class ViewModel
{
public string Info { get; set; }
}
Your View(Window) and ViewModel(class) are various parts of application. You should use DataContext property to interact between View and ViewModel and create properties, not fields like you've done in your case. For example:
View:
<Grid>
<TextBlock Text="{Binding DisplayTime}" />
</Grid>
ViewModel:
public class MyViewModel
{
private string displayTime=DateTime.Now.ToString;
public string DisplayTime
{
get { return displayTime; }
set { displayTime = value; }
}
}
There are many approaches to set DataContext:
The first approach. In view:
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
The second approach. You should override OnStartUp() method of App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow app = new MainWindow();
ProductViewModel context = new ProductViewModel();
app.DataContext = context;
app.Show();
}
}
The third approach. In constructor of Windows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext=new MainWindowViewModel();
}
}
The fourth approach. You can set DataContext through DependencyInjection by UnityContainer. But DependencyInjection, Prism and UnityContainer are other questions and goes from this scope of the question. Just for example:
protected override void RegisterTypes()
{
unityContainer.RegisterType<object, ItemControl>("ModuleAUpper");
unityContainer.RegisterType<IViewModelItemControl, ViewModelItemControl>();
unityContainer.RegisterTypeForNavigation<ItemControl>();
}

Categories