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
Related
I did a very simple repro for my case:
MyControl1.xaml.cs:
public sealed partial class MyUserControl1 : UserControl
{
public ICommand SendInputCommand
{
get { return (ICommand)GetValue(SendInputCommandProperty); }
set { SetValue(SendInputCommandProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SendInputCommandProperty =
DependencyProperty.Register("MyProperty", typeof(ICommand), typeof(MyUserControl1), new PropertyMetadata(null));
public MyUserControl1()
{
this.InitializeComponent();
}
}
MyControl1.xaml:
<UserControl
x:Class="TestBindingSolution.MyUserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestBindingSolution"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
x:Name="mycontrol1">
<Grid>
<Button Content="ttt" Command="{x:Bind SendInputCommand}" />
</Grid>
MainPage.xaml:
<Page
x:Class="TestBindingSolution.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestBindingSolution"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewmodels="using:TestBindingSolution.ViewModels"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
x:Name="MyPage"
Loaded="Page_Loaded">
<Page.DataContext>
<viewmodels:MainPageViewModel x:Name="ViewModel"/>
</Page.DataContext>
<StackPanel>
<Button Content="ssss" Command="{Binding DataContext.SendInputCommand,ElementName=MyPage}"/>
<local:MyUserControl1 SendInputCommand="{x:Bind ViewModel.SendInputCommand}" />
</StackPanel>
And finally MainPageViewModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using TestBindingSolution.Custom;
namespace TestBindingSolution.ViewModels
{
public class MainPageViewModel : Observable
{
#region Commands
public RelayCommand SendInputCommand { get; }
#endregion
public MainPageViewModel()
{
this.SendInputCommand = new RelayCommand(this.OnSendInputCommand,this.CanExecuteSendInputCommand);
}
private bool CanExecuteSendInputCommand(object parameter)
{
return true;
}
private void OnSendInputCommand(object parameter)
{
throw new NotImplementedException();
}
}
}
Everything works as aspected using x:Bind but if i replace x:Bind ... with "{Binding DataContext.SendInputCommand, ElementName:MyPage}" i have an unhandled exception in InitializeComponent in the mainpage.xaml.cs
If you use the same bind the Command property of a button it works perfectly.
This is drive me crazy, where is my mistake?
UPDATE ----
below the classes i use for relaycommand and observable
public abstract class Observable : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual bool SetValue<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(storage, value))
return false;
storage = value;
this.RaisePropertyChanged(propertyName);
return true;
}
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
and here the RelayCommand
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
private Action<object> _commandAction;
private Func<object, bool> _canExecuteFunction;
public RelayCommand(Action<object> commandAction)
: this(commandAction, null)
{
}
public RelayCommand(Action<object> commandAction, Func<object, bool> canExecuteFunction)
{
this._commandAction = commandAction;
this._canExecuteFunction = canExecuteFunction;
}
public void RaiseCanExecuteChanged()
{
if (this.CanExecuteChanged != null)
{
CanExecuteChanged.Invoke(this, EventArgs.Empty);
}
}
public bool CanExecute(object parameter)
{
bool returnValue = true;
if (this._canExecuteFunction != null)
{
returnValue = this._canExecuteFunction.Invoke(parameter);
}
return returnValue;
}
public void Execute(object parameter)
{
if (this._commandAction != null)
{
this._commandAction.Invoke(parameter);
}
}
}
}
There is a little mistake when you are trying to register the SendInputCommand dependency property in the UserControl. You are registering the property name as MyProperty not SendInputCommand. So the binding failed to find the correct dependency property in your UserControl. That's why this behavior only happens for the UserControl.
Please change the code like this. Modify MyProperty to SendInputCommand.
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SendInputCommandProperty =
DependencyProperty.Register("SendInputCommand", typeof(ICommand), typeof(MyUserControl1), new PropertyMetadata(null));
Now your code should work correctly even using Binding.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 3 years ago.
Improve this question
I have a User control with a button. when click on the button create a random number. I want use this number in Main Window. I use user control for 3 time in my Main Window. but I can't get the number in main window. I use MVVM.
Please help me!
UserControl.xaml
<UserControl x:Class="test.UserControl1"
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:test"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="300">
<Grid>
<Grid.DataContext>
<local:UserControl1VM/>
</Grid.DataContext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=txt}"/>
<Button Grid.Column="1" Content="Show Num" Command="{Binding Path=AddNum}"/>
</Grid>
</UserControl>
User Contorl View Model
public class UserControl1VM : INotifyPropertyChanged
{
private string _txt;
public string txt
{
get { return _txt; }
set
{
_txt = value;
OnPropertyChanged("txt");
}
}
public RelayCommand AddNum { get; set; }
public UserControl1VM()
{
AddNum = new RelayCommand(DoAddNum);
}
private void DoAddNum(object obj)
{
Random random = new Random();
txt = random.Next(0, 100).ToString();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
MainWindow.xaml
<Window x:Class="test.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:test"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<UniformGrid Rows="4">
<local:UserControl1 Margin="20"/>
<local:UserControl1 Margin="20"/>
<local:UserControl1 Margin="20"/>
<UniformGrid Columns="2" Margin="20">
<TextBlock Text="{Binding Path=Sum}"/>
<Button Content="Sum" Command="{Binding Path=AddSum}"/>
</UniformGrid>
</UniformGrid>
Main Window View Model
public class MainWindowVM : INotifyPropertyChanged
{
private string _Sum;
public string Sum
{
get { return _Sum; }
set
{
_Sum = value;
OnPropertyChanged("Sum");
}
}
public RelayCommand AddSum { get; set; }
public MainWindowVM()
{
AddSum = new RelayCommand(DoAddSum);
}
private void DoAddSum(object obj)
{
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
private string v;
private Action<object> MyAction { get; set; }
public RelayCommand(Action<object> MyAction)
{
this.MyAction = MyAction;
}
public RelayCommand(string v)
{
this.v = v;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
MyAction(parameter);
}
}
I want When click on "Sum" show Sum of 3 numbers in the text block.
My suggestions
change 'sum' and 'txt' property types to int - sum up is easier on numbers ;-)
use a composite pattern on your ViewModels. Let the MainWindow view model consist of three UserControlVM's.
Changes
1.) Changes to MainWindow.xaml
<UniformGrid Rows="4">
<UniformGrid.DataContext>
<local:MainWindowVM/>
</UniformGrid.DataContext>
<local:UserControl1 Margin="20" DataContext="{Binding UserControlVM1}"/>
<local:UserControl1 Margin="20" DataContext="{Binding UserControlVM2}"/>
<local:UserControl1 Margin="20" DataContext="{Binding UserControlVM3}"/>
2.) Remove from UserControl1.xaml:
<Grid.DataContext>
<local:UserControl1VM/>
</Grid.DataContext>
3.) Change MainWindowVM.cs
public class MainWindowVM : INotifyPropertyChanged
{
private readonly List<UserControl1VM> childVms;
public UserControl1VM UserControlVM1 { get; set; }
public UserControl1VM UserControlVM2 { get; set; }
public UserControl1VM UserControlVM3 { get; set; }
public MainWindowVM()
{
childVms = new List<UserControl1VM>()
{
new UserControl1VM(),
new UserControl1VM(),
new UserControl1VM(),
};
UserControlVM1 = childVms[0];
UserControlVM2 = childVms[1];
UserControlVM3 = childVms[2];
AddSum = new RelayCommand(DoAddSum);
}
private int _Sum;
public int Sum
{
get { return _Sum; }
set
{
_Sum = value;
OnPropertyChanged("Sum");
}
}
public RelayCommand AddSum { get; set; }
private void DoAddSum(object obj)
{
childVms.ForEach(vm => Sum += vm.txt);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
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();
}
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.
I noticed that it happends not only in the one project but on multiple too so I will provide simple example. I've got such xaml:
<Page
x:Class="TestApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:TestApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Button Content="Button" Command="{Binding PressedButton}" HorizontalAlignment="Left" Margin="0,-10,0,-9" VerticalAlignment="Top" Height="659" Width="400"/>
</Grid>
</Page>
my classes to binding data:
public abstract class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
var e = new PropertyChangedEventArgs(propertyName);
this.PropertyChanged(this, e);
}
}
}
public class Command : ICommand
{
private Action<object> action;
public Command(Action<object> action)
{
this.action = action;
}
public bool CanExecute(object parameter)
{
if (action != null)
{
return true;
}
else
{
return false;
}
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (action != null)
{
action((string)parameter);
}
}
}
public class TestViewModel : ObservableObject
{
public ICommand PressedButton
{
get
{
return new Command((param) => { });
}
}
}
and main page:
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Required;
DataContext = new TestViewModel();
}
It's weird but PressedButton runs only on application start(isn't that weird it runs on start?). After that, even after button click there is nothing triggered. I can't figure out what's wrong.
I think you may be causing binding issues by returning a new command each time the "getter" is called. Try setting the command once, in your constructor (for example).
public MainPage()
{
PressedAdd = new Command(param => SaveNote());
}
public ICommand PressedAdd { get; private set; }
In the SaveNote() method, you could test the values and either save (or not save) them:
private void SaveNote()
{
if (NoteTitle == null || NoteContent == null)
return;
// Do something with NoteTitle and NoteContent
}