I'm trying to call a function in the viewmodel on startup the MVVM way. I thought I had it correct, but the code is never hitting the function call.
Here's my xaml:
<Window x:Class="TestWin.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:l="clr-namespace:Timeserver"
xmlns:viewmodel="clr-namespace:Timeserver.ViewModels"
Title="MainWindow"
Width="893"
Height="Auto"
Background="LightGray"
ResizeMode="NoResize"
SizeToContent="Height">
<Window.DataContext>
<viewmodel:MainWindowViewModel />
</Window.DataContext>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadData}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Here's my viewmodel (the necessary parts):
namespace TestWin.ViewModels
{
class MainWindowViewModel
{
private StructsModel model; // my model class
private ICommand loadDataCmd;
private ICommand showTimeWindowCmd;
private ICommand toggleExecuteCommand { get; set; }
private bool canExecute = true;
public bool CanExecute
{
get
{
return this.canExecute;
}
set
{
if (this.canExecute == value)
{
return;
}
this.canExecute = value;
}
}
public ICommand ToggleExecuteCommand
{
get
{
return toggleExecuteCommand;
}
set
{
toggleExecuteCommand = value;
}
}
public ICommand ShowTimeWindowCmd
{
//code here
}
public ICommand LoadDataCmd
{
get
{
return loadDataCmd;
}
set
{
loadDataCmd = value;
}
}
public void LoadData(object parameter)
{
model.GetData();
}
public MainWindowViewModel()
{
this.model = new StructsModel();
LoadDataCmd = new RelayCommand(LoadData, param => this.canExecute);
ShowTimeWindowCmd = new RelayCommand(ShowTimeWindow, param => this.canExecute);
toggleExecuteCommand = new RelayCommand(ChangeCanExecute);
}
public void ShowTimeWindow(object parameter)
{
//code here
}
public void ChangeCanExecute(object obj)
{
canExecute = !canExecute;
}
}
}
The function in question that is not being hit is LoadData(). It calls GetData() in my model class, which I cannot show for reasons. Not sure what else to try. I've seen other questions on SO doing the same thing I'm doing. My other function ShowTimeWindow is set up the exact same way and does get hit.
If you really want the call to be made at Loaded, you could remove the xaml code
<Window.DataContext>
<viewmodel:MainWindowViewModel />
</Window.DataContext>
and bind the viewmode from code behind, like Mat said above. Then, the Command you have bound will be triggered (if it has the same name as the one in the viewmodel), and you will not need to call the vm.LoadData() in the constructor.
Also, if the command is not used for anything else than to load data at "Loaded", CanExecute is useless.
You can create your ViewModel in code behind. Than you can call the methods you need. If you want to go for excellence you can use a Factory pattern or a dependency injection container (e.g. Windsor)
public MainWindow()
{
InitializeComponent();
MainWindowViewModel vm = new MainWindowViewModel();
DataContext = vm;
vm.LoadData();
}
Related
I have an issue with the Loaded event of the window, so I am using NuGet package
I did everything required to use the package from this link https://devblogs.microsoft.com/dotnet/open-sourcing-xaml-behaviors-for-wpf/
My xaml:
<Window x:Class="TestDynamicWindow.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:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TestDynamicWindow" d:DataContext="{d:DesignInstance Type=local:MainViewModel}"
mc:Ignorable="d"
Title="UserWindow" Height="450" Width="800"
ResizeMode="NoResize"
Background="Bisque"
>
<b:Interaction.Triggers>
<b:EventTrigger EventName="Loaded">
<b:InvokeCommandAction
CommandParameter="{Binding ElementName=ButtonsListBox, Path=Items.Count}"
Command="{Binding LoadDataCommand}"/>
</b:EventTrigger>
</b:Interaction.Triggers>
Window's DataContext is MainViewModel class:
public class MainViewModel
{
private readonly string path = $"{Environment.CurrentDirectory}\\LogInModels.xml";
public ObservableCollection<LinkModel> linkModels { get; set; } = new ObservableCollection<LinkModel>();
public ObservableCollection<LogInModel> LogInModels { get; set; }
public ICommand LoadDataCommand { get; set; }
public ICommand AddLinkCommand { get; set; }
public MainViewModel()
{
this.LoadDataCommand = new CommandInterface(LoadData, CanLoadData);
this.AddLinkCommand = new CommandInterface(AddLink, CanAddLink);
}
#region LoadDataMethods
public void LoadData(object parameter)
{
SaveOrGetData saveOrGet = new SaveOrGetData(path);
LogInModels = saveOrGet.GetData();
for(int i = 0; i < LogInModels.Count; i++)
{
LinkModel lm = new LinkModel(parameter);
linkModels.Add(lm);
}
}
public bool CanLoadData(object parameter)
{
return true;
}
}
As you can see in the MainViewModel constructor LoadDataCommand should fire LoadData() method, but I put a breakpoint on that line, and when the Window is loaded nothing happens. I don't get any error, it just simply doesn't work. I am new to this concept so I don't know what is going wrong. I think I am using InteractionTriggers in the wrong way but can't find anything that would help to use it in a proper way.
CommandInterface class is just class that implements ICommand
class CommandInterface : ICommand
{
Action<object> executeMethod;
Func<object, bool> canExecuteMethod;
public CommandInterface(Action<object> executeMethod, Func<object, bool> canExecuteMethod)
{
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
executeMethod(parameter);
}
public event EventHandler CanExecuteChanged;
}
I don't know why, but d:DataContext="{d:DesignInstance Type=local:MainViewModel}" this doesn't work for Loaded event, so after I set DataContext of View to MainViewModel instance in code behind(after InitializeComponents) it worked. If it doesn't work just change ICommand implementation to one that is in the answer of this question: How to use the CanExecute Method from ICommand on WPF
I am struggling for one week now with my problem and i cant solve it. I am programming a MVVM WPF application which is having one window (MainView). In This Mainview i want to load different UserControl when i need them. In the Application-Startup I am loading the MainViewModel. In the Constructor of the MainViewModel I am loading the First ViewModel (LoginViewModel). Cause of my MainView.xaml it is showing me my Login-User-Control like i want to. So till this point everything is fine. In the ActivePanel-class i am saving the CurrentView, because in my MainView.xaml i am making a binding to my CurrentView for the ContentControl. So everything is working except the changing of the views although my NotifyPropertyChanged method of the CurrentView is working. I am thinking, that my mistake is in the xaml (DataBinding). Hope you guys can help me.
This is my MainView.xaml in which i want to load the different DataTemplates. Like I said before: The loading of the LoginViewModel via the Constructor of MainViewModel is working. The changing to other VieModels is working as well, but the DataBinding to the ContentControl is the big problem here.
<Window x:Class="App.View.MainView"
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:App.View"
mc:Ignorable="d"
xmlns:viewmodels="clr-namespace:App.ViewModels"
xmlns:views="clr-namespace:App.View"
xmlns:helper="clr-namespace:App.Helper"
Title="Betrooms" Height="500" Width="350">
<Window.Resources>
<DataTemplate x:Name="LoginUCTemplate" DataType="{x:Type viewmodels:LoginViewModel}">
<views:LoginUC DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="RegUCTemplate" DataType="{x:Type viewmodels:RegViewModel}">
<views:RegUC DataContext="{Binding}"/>
</DataTemplate>
<DataTemplate x:Name="HomeUCTemplate" DataType="{x:Type viewmodels:HomeViewModel}">
<views:HomeUC DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Window.DataContext>
<viewmodels:ActivePanel/>
</Window.DataContext>
<Grid>
<ContentControl Content="{Binding CurrentView, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"/>
</Grid>
</Window>
This is the class of my ActivePanel where i am saving the information about which ViewModel is the active one. The CurrentView is the property I am binding the Content Control to.
namespace APP.ViewModels
{
public class ActivePanel : NotifyPropertyChanged
{
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
if (value != _currentView)
{
_currentView = value;
OnPropertyChanged("CurrentView");
}
}
}
}
}
This is my MainViewModel:
namespace App.ViewModels
{
public class MainViewModel : ActivePanel
{
private LoginViewModel _loginViewModel;
public MainViewModel()
{
_loginViewModel = new LoginViewModel();
CurrentView = _loginViewModel;
}
}
}
And this is my LoginViewModel where I am changing the value of CurrentView via an action:
namespace App.ViewModels
{
public class LoginViewModel : ActivePanel
{
#region Member
private string _username;
private string _password;
bool login = false;
private HomeViewModel _homeViewModel;
private RegViewModel _regViewModel;
UserModel User = new UserModel();
#endregion
#region Properties
public ICommand RegActionCommand { get; set; }
public ICommand LogActionCommand { get; set; }
public string GetUsername
{
get { return _username; }
set
{
if (value != _username)
{
_username = value;
OnPropertyChanged("GetUsername");
}
}
}
public string GetPassword
{
get { return _password; }
set
{
if (value != _password)
{
_password = value;
OnPropertyChanged("GetPassword");
}
}
}
#endregion
#region Constructor
public LoginViewModel()
{
this.RegActionCommand = new RelayCommand(RegAction);
this.LogActionCommand = new RelayCommand(LogAction);
}
#endregion
#region Button-Action
private void LogAction(object obj)
{
_homeViewModel = new HomeViewModel();
CurrentView = _homeViewModel;
}
private void RegAction(object obj)
{
_regViewModel = new RegViewModel();
CurrentView = _regViewModel;
}
#endregion
}
}
I hope my question is understandable: The ContenControl binding is set to CurrentView but the ContentControl is never changing although the property of CurrentView is changing.
Thanks to you all. Cheers, Paul.
In your command handler, you are changing the CurrentView property of your LoginViewModel. The ContentControl's DataContext is the MainViewModel though, so it's content is bound to the CurrentView property of the MainViewModel.
You need to set the MainViewModel's property in your command handler. There are different ways of achieving this, for example you could add a constructor parameter to the LoginViewModel to pass a reference to the MainViewModel. You can save this reference and then access it in your command handler.
Another possibilty would be to raise an event or send a message from the command in your LoginViewModel and handle it in the MainViewModel. This would reduce the coupling between your ViewModels, but depending on which mechanism and library you use it might be a little bit more complicated.
I'm developing my first application in WPF with the pattern MVC and I have a question.
I have created a usercontrol from type Grid to made a custom title bar. This grid contains a X button and I want to associate this button to a command.
My grid in XAML:
<Grid x:Class="Views.TitleBarView"
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"
mc:Ignorable="d"
Style="{DynamicResource TitleStyleGrid}"
x:Name="barView">
<Label x:Name="labelAppName" Style="{DynamicResource TitleStyleLabel}" Content="Title"/>
<Button x:Name="bttnClose" Style="{DynamicResource ButtonStyleCloseWindow}" Command="{Binding CloseCommand}"/>
<Button x:Name="buttonMinimize" Style="{DynamicResource ButtonStyleMinimizeWindow}" Command="{Binding MinimizeCommand}"/>
</Grid>
The C# view:
public partial class TitleBarView : Grid
{
public TitleBarView()
{
InitializeComponent();
TitleBarViewModel tvm = new TitleBarViewModel();
tvm.RequestClose += (s, e) => this.Close();
tvm.RequestMinimize+= (s, e) => this.Minimize();
DataContext = tvm;
}
private void Close()
{
Window.GetWindow(this).Close();
}
private void Minimize()
{
Application.Current.MainWindow.WindowState = System.Windows.WindowState.Minimized;
}
}
The C# viewModel:
public class TitleBarViewModel : ViewModelBase, IRequestMinimizeViewModel, IRequestCloseViewModel
{
private RelayCommand minimizeCommand;
protected RelayCommand closeCommand;
public event EventHandler RequestMinimize;
public event EventHandler RequestClose;
#region MinimizeCommand
public ICommand MinimizeCommand
{
get
{
if (minimizeCommand == null)
{
minimizeCommand = new RelayCommand(Minimize);
}
return minimizeCommand;
}
}
private void Minimize()
{
RequestMinimize(this, null);
}
#endregion
#region CloseCommand
public ICommand CloseCommand
{
get
{
if (closeCommand == null)
{
closeCommand = new RelayCommand(Close);
}
return closeCommand;
}
}
protected void Close()
{
RequestClose(this, null);
}
#endregion
}
I saw that it's not recommended to set a DataContext on a userControl. And when I do this, I can't change the close command. For example I want that when the main windows calls command close it calls Application.Current.Shutdown(); instead of Application.Current.Shutdown();
I know that I have something wrong but I'm too confuse to solve it. Can you explain me how to create command for userControl ? (Or just tell me what I'm doing wrong)
Thank you
ICommand:
public class CMDAddEditUser : ICommand
{
public event EventHandler CanExecuteChanged;
public VMAddEditUser ViewModel { get; set;}
public CMDAddEditUser()
{
}
public CMDAddEditUser(VMAddEditUser vm)
{
ViewModel = vm;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.ViewModel.SimpleMethod();
}
}
ViewModel:
public class VMAddEditUser
{
private Employee _employee = new Employee();
private CMDAddEditUser Command { get; set; }
public VMAddEditUser()
{
Command = new CMDAddEditUser(this);
}
public string txtFirstName
{
get { return _employee.FirstName; }
set { _employee.FirstName = value; }
}
public void SimpleMethod()
{
txtFirstName = "abc";
}
}
XAML:
<Window x:Class="WPF.AddEditUserView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel"
Title="AddEditUserView" Height="392.329" Width="534.143">
<Grid Margin="0,0,2,-3">
<Grid.Resources>
<vm:VMAddEditUser x:Key="abx"/>
</Grid.Resources>
<Grid.DataContext>
<vm:VMAddEditUser/>
</Grid.DataContext>
<Button x:Name="btn" Content="Cancel" Command="{Binding SimpleMethod, Source={StaticResource abx}}"/>
</Grid>
</Window>
The CMDAddEditUser and VMAddEditUser is in the same project while the xaml is in a different project.
The .Execute(Object Parameter) of the ICommand doesn't seem to work. I can't bind the SimpleMethod with the button that I have. When I type the Command Binding in the xaml file, the auto-complete/suggestions only shows the txtFirstName and not the SimpleMethod. I can't figure out why the SimpleMethod can't be binded and can't be found. What did I do wrong in this code?
First: All properties you want your view to be able to bind to, must be public. Since view binds to property, which is instance of ICommand implementation, property must be public, so view can access it. However, your SimpleMethod() can be private if you don't wanna expose it to the outside world, that why you have command calling it instead of letting view directly call it.
Second: You set you grids DataContext to your 'VMEditUser' class, so in binding there is no need to specify Source, DataContext is source.
This is a silverlight application, Im using asynchronous filtering for an autocompletebox, the problem is that so far i fail to bind FilterAsyncCommand property from the behaviour with the corresponding ViewModel property.
Following is the xaml declaration for the control in the View:
<controls:ChildWindow x:Class="MyApp.Views.View1"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:fw ="clr-namespace:NetBoxSys.Views"
...
>
<sdk:AutoCompleteBox
MinimumPrefixLength="3" MinimumPopulateDelay="150"
SelectedItem="{Binding Path=...}" ItemsSource="{Binding Path=...}" >
<i:Interaction.Behaviors>
<fw:FilterAsyncBehavior FilterAsyncCommand="{Binding Path=FilterAsyncCommand}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
</controls:ChildWindow>
...ViewModel code:
private ICommand filterAsyncCommand;
public ICommand FilterAsyncCommand {
get { return filterAsyncCommand; }
set {
filterAsyncCommand = value;
this.OnPropertyChanged( "FilterAsyncCommand" );
}
}
...and this is how im loading the View
var view = new View1();
view.DataContext = new ViewModel1();
view.Show(); //Modal
I have tried this syntax too but does not work either:
<fw:FilterAsyncBehavior FilterAsyncCommand="{Binding FilterAsyncCommand}" />
Need advice for this kind of binding.
UPDATE:
Behaviour code:
public class FilterAsyncBehavior : Behavior<AutoCompleteBox>
{
public ICommand FilterAsyncCommand
{
get
{
return (ICommand)GetValue(FilterAsyncCommandProperty);
}
set
{
SetValue(FilterAsyncCommandProperty, value);
}
}
public static readonly DependencyProperty FilterAsyncCommandProperty = DependencyProperty.Register("FilterAsyncCommand",
typeof(ICommand),
typeof(FilterAsyncBehavior),
new PropertyMetadata(null));
protected override void OnAttached()
{
base.OnAttached();
// handle the populating event of the associated auto complete box
AssociatedObject.Populating += AssociatedObject_Populating;
}
protected override void OnDetaching()
{
// detach the event handler
AssociatedObject.Populating -= AssociatedObject_Populating;
base.OnDetaching();
}
private void AssociatedObject_Populating(object sender, PopulatingEventArgs e)
{
// get the command
ICommand filterCommand = FilterAsyncCommand;
if (filterCommand != null)
{
// create the parameters for the command
var parameters = new FilterAsyncParameters(AssociatedObject.PopulateComplete, e.Parameter);
// execute command
filterCommand.Execute(parameters);
// cancel the population of the auto complete box
e.Cancel = true;
}
}
}
ViewModel code:
public class ViewModel1 : ViewModel, IViewModel {
public ViewModel1() {
//Initializing the command in constructor
FilterAsyncCommand = new DelegateCommand<FilterAsyncParameters>( ExecuteFilterAsync );
}
private void ExecuteFilterAsync( FilterAsyncParameters args ) {
....
}
private ICommand filterAsyncCommand;
public ICommand FilterAsyncCommand {
get { return filterAsyncCommand; }
set {
filterAsyncCommand = value;
this.OnPropertyChanged( "FilterAsyncCommand" );
}
}
}
I find the solution for my problem. I give up with passing the ViewModel at runtime, so i changed to the following implementation of the MVVM pattern
<controls:ChildWindow x:Class="MyApp.Views.View1"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:fw ="clr-namespace:NetBoxSys.Views"
...
>
<controls:ChildWindow.Resources>
<viewModel:ViewModelRecepcionOCViewModelLocator x:Key="viewModelLocator"/>
</controls:ChildWindow.Resources>
<controls:ChildWindow.DataContext>
<Binding Source="{StaticResource viewModelLocator}" Path="ViewModel" />
</controls:ChildWindow.DataContext>
<sdk:AutoCompleteBox HorizontalAlignment="Stretch" Margin="2" x:Name="remitente_Autocomplete" VerticalAlignment="Center" Grid.Column="1" Grid.Row="4"
MinimumPrefixLength="3" MinimumPopulateDelay="150" Padding="0" Height="23" TabIndex="1" Text="{Binding Path=NombreRemitente,Mode=TwoWay}"
SelectedItem="{Binding Path=SelectedRemitente,Mode=TwoWay}" ItemsSource="{Binding Path=Remitentes}" >
<i:Interaction.Behaviors>
<fw:FilterAsyncBehavior FilterAsyncCommand="{Binding Source={StaticResource viewModelLocator},Path=ViewModel.FilterAsyncCommand}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
</controls:ChildWindow>
The important part here is the binding definition for FilterAsyncCommand.
And RecepcionOCViewModelLocator is defined like this
public class RecepcionOCViewModelLocator : ViewModelLocatorBase<IRecepcionOCViewModel>
{
public IRecepcionOCViewModel ViewModel{get;set;}
}