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>();
}
Related
I've spent some time trying to solve this problem but couldn't find a solution.
I am trying to bind commands and data inside an user control to my view model. The user control is located inside a window for navigation purposes.
For simplicity I don't want to work with Code-Behind (unless it is unavoidable) and pass all events of the buttons via the ViewModel directly to the controller. Therefore code-behind is unchanged everywhere.
The problem is that any binding I do in the UserControl is ignored.
So the corresponding controller method is never called for the command binding and the data is not displayed in the view for the data binding. And this although the DataContext is set in the controllers.
Interestingly, if I make the view a Window instead of a UserControl and call it initially, everything works.
Does anyone have an idea what the problem could be?
Window.xaml (shortened)
<Window x:Class="Client.Views.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:Client.Views"
mc:Ignorable="d">
<Window.Resources>
<local:SubmoduleSelector x:Key="TemplateSelector" />
</Window.Resources>
<Grid>
<StackPanel>
<Button Command="{Binding OpenUserControlCommand}"/>
</StackPanel>
<ContentControl Content="{Binding ActiveViewModel}" ContentTemplateSelector="{StaticResource TemplateSelector}">
<ContentControl.Resources>
<DataTemplate x:Key="userControlTemplate">
<local:UserControl />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
</Grid>
</Window>
MainWindowViewModel (shortened)
namespace Client.ViewModels
{
public class MainWindowViewModel : ViewModelBase
{
private ViewModelBase mActiveViewModel;
public ICommand OpenUserControlCommand { get; set; }
public ViewModelBase ActiveViewModel
{
get { return mActiveViewModel; }
set
{
if (mActiveViewModel == value)
return;
mActiveViewModel = value;
OnPropertyChanged("ActiveViewModel");
}
}
}
}
MainWindowController (shortened)
namespace Client.Controllers
{
public class MainWindowController
{
private readonly MainWindow mView;
private readonly MainWindowViewModel mViewModel;
public MainWindowController(MainWindowViewModel mViewModel, MainWindow mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.OpenUserControlCommand = new RelayCommand(ExecuteOpenUserControlCommand);
}
private void OpenUserControlCommand(object obj)
{
var userControlController = Container.Resolve<UserControlController>(); // Get Controller instance with dependency injection
mViewModel.ActiveViewModel = userControlController.Initialize();
}
}
}
UserControlSub.xaml (shortened)
<UserControl x:Class="Client.Views.UserControlSub"
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:Client.Views"
xmlns:viewModels="clr-namespace:Client.ViewModels"
mc:Ignorable="d">
<Grid>
<ListBox ItemsSource="{Binding Models}" SelectedItem="{Binding SelectedModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Attr}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel>
<Button Command="{Binding Add}">Kategorie hinzufügen</Button>
</StackPanel>
</Grid>
</UserControl>
UserControlViewModel (shortened)
namespace Client.ViewModels
{
public class UserControlViewModel : ViewModelBase
{
private Data _selectedModel;
public ObservableCollection<Data> Models { get; set; } = new ObservableCollection<Data>();
public Data SelectedModel
{
get => _selectedModel;
set
{
if (value == _selectedModel) return;
_selectedModel= value;
OnPropertyChanged("SelectedModel");
}
}
public ICommand Add { get; set; }
}
}
UserControlController (shortened)
namespace Client.Controllers
{
public class UserControlController
{
private readonly UserControlSub mView;
private readonly UserControlViewModel mViewModel;
public UserControlController(UserControlViewModel mViewModel, UserControlSub mView)
{
this.mViewModel = mViewModel;
this.mView = mView;
this.mView.DataContext = mViewModel;
this.mViewModel.Add = new RelayCommand(ExecuteAddCommand);
}
private void ExecuteAddCommand(object obj)
{
Console.WriteLine("This code gets never called!");
}
public override ViewModelBase Initialize()
{
foreach (var mod in server.GetAll())
{
mViewModel.Models.Add(mod);
}
return mViewModel;
}
}
}
So I have created a button and I link it to an ICommand NavigateToVM but it does not hit the execute function when I try to click the button. I am I doing something wrong. I'll post the code that is relevant. Thanks in advance.
The Profile button is the button I am trying to get to work. It is just a standard Icommand.
{
public class NavigationBarVM : BaseViewModel
{
public ICommand NavigateToVMCmd { get; set; }
public NavigationBarVM()
{
}
public NavigationBarVM( NavigationStore _navigationStore)
{
NavigateToVMCmd = new NavigateToProfileCommand(this, _navigationStore);
}
}
}
namespace WpfNotes.Commands
{
public class NavigateToProfileCommand : CommandBase
{
private readonly NavigationBarVM navBarVM;
private readonly NavigationStore _navigationStore;
public NavigateToProfileCommand(NavigationBarVM VM, NavigationStore navigationStore)
{
navBarVM = VM;
_navigationStore = navigationStore;
}
public override void Execute(object parameter)
{
_navigationStore.CurrentViewModel = new ProfileVM();
Debug.WriteLine("Stuff");
}
}
}
<UserControl x:Class="WpfNotes.View.NavigationBar"
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:WpfNotes.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
xmlns:VM ="clr-namespace:WpfNotes.ViewModel">
<UserControl.Resources>
<VM:NavigationBarVM x:Key="vm"/>
</UserControl.Resources>
<Border BorderBrush="Black" BorderThickness="2">
<UniformGrid Columns="4" Height="40">
<Button BorderThickness="0" Content="Profile" Command="{Binding Source={StaticResource vm}, Path = NavigateToVMCmd }"></Button>
</UniformGrid>
</Border>
</UserControl>
You should instantiate the NavigationBarVM programmatically and inject it with a NavigationStore:
public partial class NavigationBar : UserControl
{
public MainWindow()
{
InitializeComponent();
DataContext = new NavigationBarVM(new NavigationStore());
}
}
You can then bind directly to the command of the view model:
<Button BorderThickness="0" Content="Profile"
Command="{Binding NavigateToVMCmd}" />
The following initializes the NavigationBarVM using the default constructor which is pretty useless and should be removed in your case as it doesn't initialize the command:
<VM:NavigationBarVM x:Key="vm"/>
In a WPF project (code below) I have a UserControl of type MyUserControl with a dependency property, called MyOrientation of type Orientation.
On the MainWindow I have 2 instances of MyUserControl, where via XAML I set the Orientation property on one to be Horizontal and the other instance to be Vertical.
I have made the MyOrientation property a DP as I want the ability to set it directly in XAML as in this example or using a binding.
My problem is that when I run the project both instances of the UserControl show up with the Orientation = Horizontal?
Could someone please tell me what I am doing wrong and how to fix it?
Many thanks in advance.
Here is the code:
MYUSERCONTROLVIEWMODEL:
public class MyUserControlViewModel : ViewModelBase
{
private Orientation _myOrientation;
public Orientation MyOrientation
{
get { return _myOrientation; }
set
{
if (_myOrientation == value)
return;
_myOrientation = value;
OnPropertyChanged();
}
}
}
MYUSERCONTROL.XAML
<UserControl x:Class="TestUserControlDPProblem.MyUserControl"
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:TestUserControlDPProblem"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid x:Name="root">
<Grid.DataContext>
<local:MyUserControlViewModel/>
</Grid.DataContext>
<StackPanel Orientation="{Binding MyOrientation}">
<TextBlock>Hello</TextBlock>
<TextBlock>There</TextBlock>
</StackPanel>
</Grid>
MYUSERCONTROL CODE BEHIND:
public partial class MyUserControl : UserControl
{
MyUserControlViewModel _vm;
public MyUserControl()
{
InitializeComponent();
_vm = root.DataContext as MyUserControlViewModel;
}
public static readonly DependencyProperty MyOrientationProperty = DependencyProperty.Register("MyOrientation", typeof(Orientation), typeof(MyUserControl), new FrameworkPropertyMetadata(Orientation.Vertical, new PropertyChangedCallback(OnMyOrientationChanged)));
private static void OnMyOrientationChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var myUserControl = o as MyUserControl;
myUserControl?.OnMyOrientationChanged((Orientation)e.OldValue, (Orientation)e.NewValue);
}
protected virtual void OnMyOrientationChanged(Orientation oldValue, Orientation newValue)
{
_vm.MyOrientation = newValue;
}
public Orientation MyOrientation
{
get
{
return (Orientation)GetValue(MyOrientationProperty);
}
set
{
SetValue(MyOrientationProperty, value);
}
}
}
MAINWINDOW.XAML
<Window x:Class="TestUserControlDPProblem.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:TestUserControlDPProblem"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel>
<local:MyUserControl Margin="10" MyOrientation="Horizontal"/>
<local:MyUserControl Margin="10" MyOrientation="Vertical"/>
</StackPanel>
</Grid>
The "internal" view model of the UserControl makes no sense and should not be there. You should instead bind directly to the dependency property by means of a RelativeSource or ElementName Binding:
<StackPanel Orientation="{Binding MyOrientation,
RelativeSource={RelativeSource AncestorType=UserControl}}">
You wouldn't even need the PropertyChangedCallback:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
}
public static readonly DependencyProperty MyOrientationProperty =
DependencyProperty.Register(
nameof(MyOrientation), typeof(Orientation), typeof(MyUserControl),
new FrameworkPropertyMetadata(Orientation.Vertical));
public Orientation MyOrientation
{
get { return (Orientation)GetValue(MyOrientationProperty); }
set { SetValue(MyOrientationProperty, value); }
}
}
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
I have a project, where I bind a checkbox's IsChecked property with a get/set in the codebehind. However, when the application loads, it doesn't update, for some reason. Intrigued, I stripped it down to its basics, like this:
//using statements
namespace NS
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private bool _test;
public bool Test
{
get { Console.WriteLine("Accessed!"); return _test; }
set { Console.WriteLine("Changed!"); _test = value; }
}
public MainWindow()
{
InitializeComponent();
Test = true;
}
}
}
XAML:
<Window x:Class="TheTestingProject_WPF_.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Viewbox>
<CheckBox IsChecked="{Binding Path=Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Viewbox>
</Grid>
And, lo and behold, when I set it to true, it did not update!
Anyone can come up with a fix, or explain why?
Thanks, it'd be appreciated.
In order to support data binding, your data object must implement INotifyPropertyChanged
Also, it's always a good idea to Separate Data from Presentation
public class ViewModel: INotifyPropertyChanged
{
private bool _test;
public bool Test
{ get { return _test; }
set
{
_test = value;
NotifyPropertyChanged("Test");
}
}
public PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
<Window x:Class="TheTestingProject_WPF_.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Viewbox>
<CheckBox IsChecked="{Binding Path=Test, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</Viewbox>
</Grid>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel{Test = true};
}
}