I am trying to bind to a property of a Model via the ViewModel in an textbox. I created a wrapper in the ViewModel to access the property of the Model. Whenever I put a break point in the get section of this wrapper it triggers, but a break point in the set section is not executed after changing the textbox content. Code is according to the following tutorial: https://www.codeproject.com/Articles/1193164/MVVM-Sample-application-with-a-focus-in-property
The BaseViewModel implements the INPC interface. I am trying to follow the MVVM "rule of thumbs" as close as possible. So no INPC implementation in the model in this case, and the view can only bind to the VM.
What am I doing wrong?
XAML:
<TextBox Grid.Column="1" VerticalAlignment="Center" Text="{Binding Path=ProgramInfo.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Model:
public class ProgramInfo
{
private string _name;
private string _description;
public string Name
{
get => _name;
set
{
_name = value;
}
}
public string Description
{
get => _description;
set
{
_description = value;
}
}
}
ViewModel:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo;
public ProgramInfo ProgramInfo
{
get => _programInfo;
set
{
_programInfo = value;
OnPropertyChanged("ProgramInfo");
}
}
}
Code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProgramInfoViewModel();
}
}
_programInfo is always null. you need to create an instance:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo = new ProgramInfo();
public ProgramInfo ProgramInfo
{
get => _programInfo;
set
{
_programInfo = value;
OnPropertyChanged("ProgramInfo");
}
}
}
or
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ProgramInfoViewModel { ProgramInfo = new ProgramInfo() };
}
}
a view model wrapper for model property will look like this:
class ProgramInfoViewModel : BaseViewModel
{
private ProgramInfo _programInfo;
private ProgramInfoViewModel(ProgramInfo programInfo)
{
_programInfo = programInfo;
}
public string ProgramInfoName
{
get => _programInfo.Name;
set
{
_programInfo.Name = value;
OnPropertyChanged("ProgramInfoName");
}
}
}
initialization:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var model = new ProgramInfo();
DataContext = new ProgramInfoViewModel(model);
}
}
binding path has to change accordingly:
<TextBox Text="{Binding Path=ProgramInfoName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
I'm having a serious headache with this problem. I really dislike store apps but am forced to use it in this case. I've only worked with XAML for a few weeks.
My question is:
How can I call a RelayCommand in my ViewModel (from my View of course) that will change the page on my view? And even better, change it using URI, so that I can pass a command parameter to file.
I'm totally lost on this. Currently I'm using this.Frame.Navigate(type type) in the View Code behind to navigate through pages.
I would really and I mean REALLY appreciate a description from a to z on what to do in this case.
I presume i could do something like building a framecontainer on my View and send it to my ViewModel and from there navigate the current frame to another. But I'm not sure how that works in Store apps.
I am really sorry for the lack of good questions, but I'm on a deadline and i need to get my View connected to my ViewModel in a proper way.. I don't like having both view codebehind as well as ViewModel code.
There are 2 ways to do this, a simple way is to pass a relay command action from the view to the view model.
public MainPage()
{
var vm = new MyViewModel();
vm.GotoPage2Command = new RelayCommand(()=>{ Frame.Navigate(typeof(Page2)) });
this.DataContext = vm;
}
<Button Command={Binding GoToPage2Command}>Go to Page 2</Button>
Another way is by using an IocContainer and DependencyInjection. This one is a more losely coupled approach.
We will need an interface for navigation page so that we don't need to reference or Know anything about PageX or any UI element assuming that your viewmodel is in a separate project that doesn't know anything about the UI.
ViewModel Project:
public interface INavigationPage
{
Type PageType { get; set; }
}
public interface INavigationService
{
void Navigate(INavigationPage page) { get; set; }
}
public class MyViewModel : ViewModelBase
{
public MyViewModel(INavigationService navigationService, INavigationPage page)
{
GotoPage2Command = new RelayCommand(() => { navigationService.Navigate(page.PageType); })
}
private ICommand GotoPage2Command { get; private set; }
}
UI Project:
public class NavigationService : INavigationService
{
//Assuming that you only navigate in the root frame
Frame navigationFrame = Window.Current.Content as Frame;
public void Navigate(INavigationPage page)
{
navigationFrame.Navigate(page.PageType);
}
}
public abstract class NavigationPage<T> : INavigationPage
{
public NavigationPage()
{
this.PageType = typeof(T);
}
}
public class NavigationPage1 : NavigationPage<Page1> { }
public class MainPage : Page
{
public MainPage()
{
//I'll just place the container logic here, but you can place it in a bootstrapper or in app.xaml.cs if you want.
var container = new UnityContainer();
container.RegisterType<INavigationPage, NavigationPage1>();
container.RegisterType<INavigationService, NavigationService>();
container.RegisterType<MyViewModel>();
this.DataContext = container.Resolve<MyViewModel>();
}
}
As Scott says you could use a NavigationService.
I would firstly create an interface this is not needed in this example but will be useful if you use Dependency Injection (good solution with viewmodels and services) in the future :)
INavigationService:
public interface INavigationService
{
void Navigate(Type sourcePage);
void Navigate(Type sourcePage, object parameter);
void GoBack();
}
NavigationService.cs will inherit INavigationService
you will need the following namespaces
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
public sealed class NavigationService : INavigationService
{
public void Navigate(Type sourcePage)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage);
}
public void Navigate(Type sourcePage, object parameter)
{
var frame = (Frame)Window.Current.Content;
frame.Navigate(sourcePage, parameter);
}
public void GoBack()
{
var frame = (Frame)Window.Current.Content;
frame.GoBack();
}
}
Simple ViewModel to show RelayCommand example. NB I Navigate to another Page (Page2.xaml) using the DoSomething RelayCommand.
MyViewModel.cs
public class MyViewModel : INotifyPropertyChanged
{
private INavigationService _navigationService;
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public MyViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
private ICommand _doSomething;
public ICommand DoSomething
{
get
{
return _doSomething ??
new RelayCommand(() =>
{
_navigationService.Navigate(typeof(Page2));
});
}
}}
In simple example Ive created the viewmodel in MainPage.cs and added the NavigationService
but you can do this elsewhere depending on what your MVVM setup is like.
MainPage.cs
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var vm = new MyViewModel(new NavigationService());
this.DataContext = vm;
}
}
MainPage.xaml (binds to the command DoSomething)
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Button Width="200" Height="50" Content="Go to Page 2"
Command="{Binding DoSomething}"/>
</Grid>
Hope that helps.
I don't really like when a ViewModel references Views to navigate to. So I prefer to a ViewModel-first approach. By using ContentControls, DataTemplates for ViewModel types & some kind of navigation pattern in my ViewModels.
My navigation looks like this:
[ImplementPropertyChanged]
public class MainNavigatableViewModel : NavigatableViewModel
{
public ICommand LoadProfileCommand { get; private set; }
public ICommand OpenPostCommand { get; private set; }
public MainNavigatableViewModel ()
{
LoadProfileCommand = new RelayCommand(() => Navigator.Navigate(new ProfileNavigatableViewModel()));
OpenPostCommand = new RelayCommand(() => Navigator.Navigate(new PostEditViewModel { Post = SelectedPost }), () => SelectedPost != null);
}
}
My NavigatableViewModel looks like:
[ImplementPropertyChanged]
public class NavigatableViewModel
{
public NavigatorViewModel Navigator { get; set; }
public NavigatableViewModel PreviousViewModel { get; set; }
public NavigatableViewModel NextViewModel { get; set; }
}
And my Navigator:
[ImplementPropertyChanged]
public class NavigatorViewModel
{
public NavigatableViewModel CurrentViewModel { get; set; }
public ICommand BackCommand { get; private set; }
public ICommand ForwardCommand { get; private set; }
public NavigatorViewModel()
{
BackCommand = new RelayCommand(() =>
{
// Set current control to previous control
CurrentViewModel = CurrentViewModel.PreviousViewModel;
}, () => CurrentViewModel != null && CurrentViewModel.PreviousViewModel != null);
ForwardCommand = new RelayCommand(() =>
{
// Set current control to next control
CurrentViewModel = CurrentViewModel.NextViewModel;
}, () => CurrentViewModel != null && CurrentViewModel.NextViewModel != null);
}
public void Navigate(NavigatableViewModel newViewModel)
{
if (newViewModel.Navigator != null && newViewModel.Navigator != this)
throw new Exception("Viewmodel can't be added to two different navigators");
newViewModel.Navigator = this;
if (CurrentViewModel != null)
{
CurrentViewModel.NextViewModel = newViewModel;
}
newViewModel.PreviousViewModel = CurrentViewModel;
CurrentViewModel = newViewModel;
}
}
My MainWindows.xaml:
<Window
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:viewmodels="clr-namespace:MyApp.ViewModels"
x:Class="MyApp.Windows.MainWindow"
mc:Ignorable="d"
Title="MainWindow" Height="389" Width="573"
d:DataContext="{d:DesignInstance {x:Type viewmodels:MyAppViewModel}, IsDesignTimeCreatable=True}">
<Grid>
<!-- Show data according to data templates as defined in App.xaml -->
<ContentControl Content="{Binding Navigator.CurrentViewModel}" Margin="0,32,0,0" />
<Button Content="Previous" Command="{Binding Navigator.BackCommand}" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Left" Margin="10,5,0,0" VerticalAlignment="Top" Width="75" />
<Button Content="Next" Command="{Binding Navigator.ForwardCommand}" Style="{DynamicResource ButtonStyle}" HorizontalAlignment="Left" Margin="90,5,0,0" VerticalAlignment="Top" Width="75" />
</Grid>
</Window>
App.xaml.cs:
public partial class App
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
new MainWindow {DataContext = new MyAppViewModel()}.Show();
}
}
MyAppViewModel:
[ImplementPropertyChanged]
public class MyAppViewModel
{
public NavigatorViewModel Navigator { get; set; }
public MyAppViewModel()
{
Navigator = new NavigatorViewModel();
Navigator.Navigate(new MainNavigatableViewModel());
}
}
App.xaml:
<DataTemplate DataType="{x:Type viewmodels:MainNavigatableViewModel}">
<controls:MainControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewmodels:PostEditViewModel}">
<controls:PostEditControl/>
</DataTemplate>
The downside is that you have more ViewModel-code which manages the state of what you are looking at. But obviously that is also a huge advantage in terms of Testability. And of course your ViewModels do not need to depend on your Views.
Plus I use Fody/PropertyChanged, that's what the [ImplementPropertyChanged] is about. Keeps me from writing OnPropertyChanged code.
Here is another way to implement the NavigationService, without using an abstract class and without referencing view types in your view model.
Assuming that the view model of the destination page is something like this:
public interface IDestinationViewModel { /* Interface of destination vm here */ }
class MyDestinationViewModel : IDestinationViewModel { /* Implementation of vm here */ }
Then your NavigationService can simply implement the following interface:
public interface IPageNavigationService
{
void NavigateToDestinationPage(IDestinationViewModel dataContext);
}
In your main window ViewModel you need to inject the navigator and the view model of the destination page:
class MyViewModel1 : IMyViewModel
{
public MyViewModel1(IPageNavigationService navigator, IDestinationViewModel destination)
{
GoToPageCommand = new RelayCommand(() =>
navigator.NavigateToDestinationPage(destination));
}
public ICommand GoToPageCommand { get; }
}
The implementation of the NavigationService encapsulates the view type (Page2) and the reference to the frame which is injected through the constructor:
class PageNavigationService : IPageNavigationService
{
private readonly Frame _navigationFrame;
public PageNavigationService(Frame navigationFrame)
{
_navigationFrame = navigationFrame;
}
void Navigate(Type type, object dataContext)
{
_navigationFrame.Navigate(type);
_navigationFrame.DataContext = dataContext;
}
public void NavigateToDestinationPage(IDestinationViewModel dataContext)
{
// Page2 is the corresponding view of the destination view model
Navigate(typeof(Page2), dataContext);
}
}
To get the frame simply name it in your MainPage xaml:
<Frame x:Name="RootFrame"/>
In the code behind of the MainPage initialize your bootstrapper by passing the root frame:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
var bootstrapper = new Bootstrapper(RootFrame);
DataContext = bootstrapper.GetMainScreenViewModel();
}
}
Finally here is the bootstrapper implementation for completeness ;)
class Bootstrapper
{
private Container _container = new Container();
public Bootstrapper(Frame frame)
{
_container.RegisterSingleton(frame);
_container.RegisterSingleton<IPageNavigationService, PageNavigationService>();
_container.Register<IMyViewModel, MyViewModel1>();
_container.Register<IDestinationViewModel, IDestinationViewModel>();
#if DEBUG
_container.Verify();
#endif
}
public IMyViewModel GetMainScreenViewModel()
{
return _container.GetInstance<IMyViewModel>();
}
}
This simply bothers me that no one has solved this at the architectural level. So this is the code for complete decoupling the views, viewmodels and the mapping between them with using the built-in Frame based navigation. The implementation uses Autofact as DI container, but can be easily ported to other IoC solutions.
Core VM logic (these should be in the same assembly):
// I would not get into how the ViewModel or property change notification is implemented
public abstract class PageViewModel : ViewModel
{
protected internal INavigationService Navigation { get; internal set; }
internal void NavigationCompleted()
{
OnNavigationCompleted();
}
protected virtual void OnNavigationCompleted()
{
}
}
public interface INavigationService
{
void Navigate<TModel>() where TModel : PageViewModel;
}
public abstract class NavigationServiceBase : INavigationService
{
public abstract void Navigate<TModel>() where TModel : PageViewModel;
protected void CompleteNavigation(PageViewModel model)
{
model.Navigation = this;
model.NavigationCompleted();
}
}
This code should be in a UWP class library or executable:
public interface INavigationMap<TModel>
where TModel: PageViewModel
{
Type ViewType { get; }
}
internal class NavigationMap<TModel, TView> : INavigationMap<TModel>
where TModel: PageViewModel
where TView: Page
{
public Type ViewType => typeof(TView);
}
public class NavigationService : NavigationServiceBase
{
private readonly Frame NavigationFrame;
private readonly ILifetimeScope Resolver;
public NavigationService(ILifetimeScope scope)
{
Resolver = scope;
NavigationFrame = Window.Current.Content as Frame;
NavigationFrame.Navigated += NavigationFrame_Navigated;
}
private void NavigationFrame_Navigated(object sender, Windows.UI.Xaml.Navigation.NavigationEventArgs e)
{
if(e.Content is FrameworkElement element)
{
element.DataContext = e.Parameter;
if(e.Parameter is PageViewModel page)
{
CompleteNavigation(page);
}
}
}
public override void Navigate<TModel>()
{
var model = Resolver.Resolve<TModel>();
var map = Resolver.Resolve<INavigationMap<TModel>>();
NavigationFrame.Navigate(map.ViewType, model);
}
}
The rest is just convenience code for registering in the DI and usage examples:
public static class NavigationMap
{
public static void RegisterNavigation<TModel, TView>(this ContainerBuilder builder)
where TModel : PageViewModel
where TView : Page
{
builder.RegisterInstance(new NavigationMap<TModel, TView>())
.As<INavigationMap<TModel>>()
.SingleInstance();
}
}
builder.RegisterNavigation<MyViewModel, MyView>();
public class UserAuthenticationModel : PageViewModel
{
protected override void OnNavigationCompleted()
{
// UI is visible and ready
// navigate to somewhere else
Navigation.Navigate<MyNextViewModel>();
}
}
I'm having an issue with databinding a textblock to a custom prop inside another class, what am I doing wrong?
mainpage:
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
InitializeLanguage();
}
private void InitializeLanguage()
{
LanguageHelper lh = new LanguageHelper();
// this.TitlePanel.DataContext = lh;
txtTitle.DataContext = lh;
}
}
databinding:
<TextBlock x:Name="txtTitle"
Text="{Binding homepage_subheading}"
Style="{StaticResource PhoneTextNormalStyle}"
Foreground="White"
Margin="12,0"/>
LanguageHelper class:
public class LanguageHelper
{
public String homepage_subheading;
public void changeLanguage()
{
if (true)
{
//english
homepage_subheading = "This is the top / sub Heading";
}
}
}
You don't have a property but a public field, and the databinding engine only works on properties.
So you need to change your class:
public String homepage_subheading { get; set; }
If you want to also notify the UI with your changes of your properties your LanguageHelper should implement the INotifyPropertyChanged interface and fire the PropertyChange event when you modify your properties.
You should declare some dependency property or use INotifyPropertyChanged, I would like to use a dependency property:
public class LanguageHelper : DependencyObject {
public static DependencyProperty Hompage_subheadingProperty =
DependencyProperty.Register("Homepage_subheading", typeof(string), typeof(LanguageHelper));
public String Homepage_subheading {
get { return (string) GetValue(Homepage_subheadingProperty);}
set { SetValue(Homepage_subheadingProperty, value);}
}
}
Note about the naming convention in C#, all properties should have first letter capitalized.
I have a problem with listView initializations. The .xaml part of the listView is as below,
<ListView x:Name="categoryListView" HorizontalAlignment="Left" Width="129" Height="180"
ItemsSource="{Binding Path=RecordModel.CategoryList}"
DisplayMemberPath="RecordModel.CategoryList"
SelectedValue="{Binding Path=RecordModel.RecordTitle}"
VerticalAlignment="Top">
I have a list of String paths in RecordModel.CategoryList but I need to change the list at window initialization. Part of the view-model is below. Where can I add the code to change the list so the listView gets the changed list items at start?
public class MainWindowViewModel : ViewModelBase
{
...
private RecordModel _recordModel;
private ICommand _addCategoryCommand;
...
public MainWindowViewModel()
{
_recordModel = new RecordModel();
}
public RecordModel RecordModel
{
get { return _recordModel; }
set { _recordModel = value; }
}
...
public ICommand AddCategoryCommand
{
get
{
if (_addCategoryCommand == null)
_addCategoryCommand = new AddCat ();
return _addCategoryCommand;
}
}
public class AddCat : ICommand
{
public bool CanExecute(object parameter) { return true; }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
MainWindowViewModel mainWindowViewModel = (MainWindowViewModel)parameter;
...
//Do things with mainWindowViewModel and the variables it has
}
...
This is the reason that ViewModels exist: so that they can transparently convert values from the Model to values more appropriate for binding.
You should expose a CategoryList property on the MainWindowViewModel and bind directly on that. You can then populate it by processing the values of RecordModel.CategoryList in the RecordModel property setter:
public class MainWindowViewModel : ViewModelBase
{
private RecordModel _recordModel;
public MainWindowViewModel()
{
RecordModel = new RecordModel(); // set the property not the field
}
public RecordModel RecordModel
{
get { return _recordModel; }
set {
_recordModel = value;
// populate CategoryList here from value.CategoryList
}
}
public UnknownType CategoryList { get; }
}
This is my Globals class
public class Globals
{
private static Globals instance = new Globals();
protected Globals()
{
}
public static Globals Instance
{
get { return instance; }
}
public TrackList Tracklist = new TrackList();
}
This is TrackList in a smart code:
public class TrackList : SettingAttribute {
public TrackList()
{
this.tracks = new ObservableCollectionExt<Track>();
}
protected ObservableCollectionExt<Track> tracks;
public ObservableCollectionExt<Track> Tracks
{
get
{
return tracks;
}
}
public class Track : ICloneable
{
protected Track()
{
// Track instance is achieved by GetTrack
}
public GetTrack(string path)
{
// code implementation here
}
}
}
I wish to bind Globals.Instance.Tracklist.Tracks in a ListView using XAML.
Via runtime, is really easy using ItemSource property
lv.ItemsSource = Globals.Instance.Tracklist.Tracks;
but using xaml I tried with several codes but none is good.
ItemsSource="{Binding Tracklist.Tracks, Source={x:Static local:Globals.Instance}}"
Tracklist has to be a property. Change your Globals class to:
public class Globals
{
private static Globals instance = new Globals();
protected Globals()
{
Tracklist = new TrackList();
}
public static Globals Instance
{
get { return instance; }
}
public TrackList Tracklist { get; private set; }
}
In you view model create Property with type Globals as follows:
property Globals Globals {get;set;}
In XAML bind to it:
<ListView ItemsSource="{Binding Path=Globals.Instance.Tracklist.Tracks}">