Im learning MVVM at the moment and made a Login Window which opens first.
After Logging in, my MainWindow opens. The MainWindows Title is set via Property in MainWindowViewModel but doesn't shown (its empty) now when I open this window via LoginWindow instead of making it the StartUp Window.
This is the Code how I open MainWindow from Login.
LoginViewModel.cs
if (r)
{
CurrentUser.Username = Username;
Messenger.Default.Send(new NotificationMessage("CloseWindow"));
}
LoginView.xaml.cs
public Login()
{
InitializeComponent();
Messenger.Default.Register<NotificationMessage>(this, (message) =>
{
switch (message.Notification)
{
case "CloseWindow":
Messenger.Default.Send(new NotificationMessage("NewCourse"));
var MainWindow = new MainWindow();
MainWindow.Show();
this.Close();
break;
}
});
}
MainViewModel.cs
public MainViewModel()
{
if (IsInDesignMode)
{
WindowTitle = "Controlcenter (Designmode)";
CurrentUserLoggedIn = "Logged in as: " + CurrentUser.Username;
CurrentVersion = "Version: " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
}
else
{
WindowTitle = "Controlcenter";
CurrentUserLoggedIn = "Logged in as: " + CurrentUser.Username;
CurrentVersion = "Version: " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
}
}
public string WindowTitle { get; private set; }
public string CurrentUserLoggedIn { get; private set; }
public string CurrentVersion { get; private set; }
I dont know why but I think MainViewModel() isn't called.
Im using MVVMLight and PropertyChanged.Fody.
So my ViewModelLocator looks like this
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<DataErrorInfoViewModel>();
SimpleIoc.Default.Register<LoginViewModel>();
}
public MainViewModel Main => ServiceLocator.Current.GetInstance<MainViewModel>();
public LoginViewModel Login => ServiceLocator.Current.GetInstance<LoginViewModel>();
public DataErrorInfoViewModel DataErrorInfo => ServiceLocator.Current.GetInstance<DataErrorInfoViewModel>();
Is there something wrong in ViewModelLocator?
Edit:
MainWindow.xaml
<Window x:Class="Ui.Desktop.MainWindow"
[...]
xmlns:logic="clr-namespace:Logic.Ui;assembly=ControlcenterMVVM.Logic.Ui"
Title="{Binding WindowTitle, Mode=OneWay}"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Window.Resources>
<DataTemplate x:Name="firmcustomerViewTemplate" DataType="{x:Type logic:FirmcustomerViewModel}">
<local:Firmcustomer DataContext="{Binding}" />
</DataTemplate>
<DataTemplate x:Name="privatecustomerViewTemplate" DataType="{x:Type logic:PrivatecustomerViewModel}">
<local:Privatecustomer DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>
<Grid>
[...]
<Label Content="{Binding CurrentUser}" FontWeight="Normal" FontSize="13" />
<Label Content="{Binding CurrentVersion}" />
</StackPanel>
<ContentControl Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" Content="{Binding}" />
</Grid>
And MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new FirmcustomerViewModel();
}
private void firmcustomer_Click(object sender, RoutedEventArgs e)
{
DataContext = new FirmcustomerViewModel();
}
private void privatecustomer_Click(object sender, RoutedEventArgs e)
{
DataContext = new PrivatecustomerViewModel();
}
}
So what does PropertyChanged.Fody for me now?
In MainWindowViewModel i just add the Properties as follows:
public string WindowTitle { get; private set; }
public string CurrentUserLoggedIn { get; private set; }
public string CurrentVersion { get; private set; }
No I compile the Project and use dotpeek to decompile the project and see, how it looks like now
public string WindowTitle
{
get
{
return this.\u003CWindowTitle\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CWindowTitle\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CWindowTitle\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (WindowTitle));
}
}
public string CurrentUserLoggedIn
{
get
{
return this.\u003CCurrentUserLoggedIn\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CCurrentUserLoggedIn\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CCurrentUserLoggedIn\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (CurrentUserLoggedIn));
}
}
public string CurrentVersion
{
get
{
return this.\u003CCurrentVersion\u003Ek__BackingField;
}
private set
{
if (string.Equals(this.\u003CCurrentVersion\u003Ek__BackingField, value, StringComparison.Ordinal))
return;
this.\u003CCurrentVersion\u003Ek__BackingField = value;
this.RaisePropertyChanged(nameof (CurrentVersion));
}
}
So RaisePropertyChaned is there.
Related
Goal
My actual goal is to navigate the ContentPresenter not by the main navigation, but via a button within the navigated page.
My current results
This is my Main navigation on the left hand side:
When clicked on either of the main navigation items, the ContentPresenter will load it's ViewModel.
Here is the Home tab
and the Some Other tab
Expected results
My expectation is to click on the button (See image below) from the loaded View Model, and navigate to the other view model...
But I am not sure how to implement such idea.
Code
Page View Model
public class PageViewModel
{
public string Title { get; set; }
public object Content { get; set; }
public List<PageViewModel> Children { get; set; }
}
Main View Model
public class MainViewModel
{
public List<PageViewModel> Navigation { get; set; }
public MainViewModel()
{
Navigation = new List<PageViewModel>
{
new PageViewModel
{
Title = "Home",
Content = new HomeViewModel()
},
new PageViewModel
{
Title = "Some Other Tab",
Content = new SomeOtherViewModel()
}
};
}
}
MainWindow.xaml
...
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView ItemsSource="{Binding Navigation}"
x:Name="Nav">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentPresenter Content="{Binding ElementName=Nav, Path=SelectedItem.Content}"
Grid.Column="1"/>
</Grid>
</Window>
Home View Model
public class HomeViewModel
{
public string SomeTitle { get; set; }
public HomeViewModel()
{
SomeTitle = "Hello Home ViewModel";
}
}
Some Other View Model
public class SomeOtherViewModel
{
public string SomeTitle { get; set; }
public SomeOtherViewModel()
{
SomeTitle = "Hello SomeOther View Model";
}
}
Question
What would be the correct implementation to navigate via the internal (child) view model?
You must implement INotifyPropertyChanged in the MainViewModel and add a property called SelectedItem to bind to the listview.
I put the code to do this below. The code works properly.
PageViewModel.cs
public class PageViewModel
{
public string Title { get; set; }
public object Content { get; set; }
}
MainViewModel.cs
public class MainViewModel : INotifyPropertyChanged
{
public List<PageViewModel> Navigation { get; set; }
private PageViewModel selectedItem { get; set; }
public PageViewModel SelectedItem
{
get { return selectedItem; }
set
{
selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
public MainViewModel()
{
Navigation = new List<PageViewModel>
{
new PageViewModel
{
Title = "Home",
Content = new HomeViewModel(this),
},
new PageViewModel
{
Title = "Some Other Tab",
Content = new SomeOtherViewModel(),
}
};
SelectedItem = Navigation.FirstOrDefault();
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
HomeViewModel.cs
public class HomeViewModel
{
public string SomeTitle { get; set; }
public object View { get; set; }
MainViewModel mainViewModel;
public RelayCommand SomeOtherCommand { get; private set; }
public HomeViewModel(MainViewModel _mainViewModel)
{
SomeTitle = "Hello Home ViewModel";
View = new View1(this);
mainViewModel = _mainViewModel;
SomeOtherCommand = new RelayCommand(SomeOtherMethod);
}
private void SomeOtherMethod(object parameter)
{
mainViewModel.SelectedItem = mainViewModel.Navigation.Where(a => a.Title == "Some Other Tab").FirstOrDefault();
}
}
SomeOtherViewModel.cs
public class SomeOtherViewModel
{
public string SomeTitle { get; set; }
public object View { get; set; }
public SomeOtherViewModel()
{
SomeTitle = "Hello SomeOther View Model";
View = new View2();
}
}
RelayCommand.cs
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
{
throw new NullReferenceException("execute");
}
else
{
_execute = execute;
_canExecute = canExecute;
}
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
public RelayCommand(Action<object> execute) : this(execute, null)
{
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute.Invoke(parameter);
}
}
MainWindow.xaml
<Window.DataContext>
<local:MainViewModel />
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel>
<ListView ItemsSource="{Binding Navigation}" x:Name="Nav" SelectedItem="{Binding Path=SelectedItem,Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate DataType="{x:Type local:PageViewModel}">
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
<ContentPresenter Content="{Binding ElementName=Nav, Path=SelectedItem.Content.View}" Grid.Column="1" />
</Grid>
View1.xaml
<Grid>
<Button x:Name="btnGet" Content="get" Height="40" Command="{Binding SomeOtherCommand}"></Button>
</Grid>
View2.xaml
<Grid>
<Button Content="test"></Button>
</Grid>
View1.cs
public partial class View1 : UserControl
{
public View1(HomeViewModel homeViewModel)
{
InitializeComponent();
DataContext = homeViewModel;
}
}
I am trying to make when clicking on a Label enter text in an Entry. I am doing it with TapGestureRecognizer and command but it does nothing, I don't know where the problem can be. The x:Name of the Entry is EntryControl.
My Label:
<Label Margin="10"
FontSize="30"
Text="{Binding EmojiSource}">
<Label.GestureRecognizers>
<TapGestureRecognizer Command="{Binding EmojiTappedCommand}" />
</Label.GestureRecognizers>
</Label>
Behind Code:
public ICommand EmojiTappedCommand { get; private set; }
public Editor(){
EmojiTappedCommand = new Command(EmojiButtonCommand);
}
private void EmojiButtonCommand()
{
EntryControl.Text ="Tapped";
}
Please add BindingContext = this; in the background code.
public partial class MainPage : ContentPage
{
public ICommand EmojiTappedCommand { get; private set; }
public string EmojiSource { get; set; }
public MainPage()
{
InitializeComponent();
EmojiSource = "test";
EmojiTappedCommand = new Command(EmojiButtonCommand);
BindingContext = this;
}
private void EmojiButtonCommand()
{
EntryControl.Text = "Tapped";
}
Here is running GIF.
The following code successfully creates two buttons dynamically, what I can not figure out is how to make the buttons open a different files when clicked.
What am I missing?
XAML:
<ItemsControl ItemsSource="{Binding DataButtons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Content="{Binding ButtonName}"
Command="{Binding ButtonCommand}"
CommandParameter="{Binding FilePath}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
ViewModel:
namespace DynamicControlsMvvmLight.ViewModel
{
public class MainViewModel : ViewModelBase
{
private readonly ObservableCollection<ButtonModel> _dataButtons = new ObservableCollection<ButtonModel>();
public ObservableCollection<ButtonModel> DataButtons { get { return _dataButtons; } }
private ICommand _buttonCommand;
public ICommand ButtonCommand
{
get {
if (_buttonCommand == null) {
_buttonCommand = new RelayCommand<object>(CommandExecute, CanCommandExecute);
}
return _buttonCommand;
}
}
public MainViewModel()
{
ButtonModel data1 = new ButtonModel("Button 1", ButtonCommand, "c:/Folder/File1.PDF");
ButtonModel data2 = new ButtonModel("Button 2", ButtonCommand, "c:/Folder/File2.PDF");
DataButtons.Add(data1);
DataButtons.Add(data2);
}
private void CommandExecute(object FilePath)
{
ButtonModel button = FilePath as ButtonModel;
System.Diagnostics.Process.Start(button.FilePath);
}
private bool CanCommandExecute(object FilePath)
{
Console.WriteLine("CanCommandExecute Method...");
return true;
}
}
}
Model:
namespace DynamicControlsMvvmLight.Model
{
public class ButtonModel
{
public string ButtonName { get; set; }
public ICommand ButtonCommand { get; set; }
public string FilePath { get; set; }
public ButtonModel(string buttonName, ICommand buttonCommand, string filePath)
{
ButtonName = buttonName;
ButtonCommand = buttonCommand;
FilePath = filePath;
}
}
}
ERROR
I get the following error when I click any of the buttons.
RelayCommand expects to receive CommandParameter which is a string in this case.
The code must look like:
public ICommand ButtonCommand
{
get
{
if (_buttonCommand == null)
{
_buttonCommand = new RelayCommand<string>(CommandExecute, CanCommandExecute);
}
return _buttonCommand;
}
}
and
private void CommandExecute(string filePath)
{
System.Diagnostics.Process.Start(filePath);
}
I am working on the ability to add and delete rows in an observable collection.
Since the original post I have created a test app that only has the ability to delete rows from the observable collection. I populate the database externally then open it in this just to test the delete function which doesn't work. It executes the RemoveAt line, deletes from the Observable Collection but the view does not update. Here is all my code:
Model:
public class TestModel : ObservableObject
{
#region Properties
private Double id;
public Double ID
{
get { return id; }
set
{
id = value;
RaisePropertyChangedEvent("ID");
}
}
private string type;
public string Type
{
get { return type; }
set
{
type = value;
RaisePropertyChangedEvent("Type");
}
}
private decimal amount;
public decimal Amount
{
get { return amount; }
set
{
amount = value;
RaisePropertyChangedEvent("Amount");
}
}
private string notes;
public string Notes
{
get { return notes; }
set
{
notes = value;
RaisePropertyChangedEvent("Notes");
}
}
#endregion
}
Viewmodel:
public class MainWindowViewModel : ObservableObject
{
#region GetData
public MainWindowViewModel()
{
Transactions = DatabaseFunctions.getTransactionData();
}
#endregion
#region ObservableCollections
private ObservableCollection<TestModel> transactions;
public ObservableCollection<TestModel> Transactions
{
get { return transactions; }
set
{
transactions = value;
RaisePropertyChangedEvent("Transactions");
}
}
#endregion
#region Properties
public static string SharedWith;
private Double id;
public Double ID
{
get { return id; }
set
{
id = value;
RaisePropertyChangedEvent("ID");
}
}
private string type;
public string Type
{
get { return type; }
set
{
type = value;
RaisePropertyChangedEvent("Type");
}
}
private decimal amount;
public decimal Amount
{
get { return amount; }
set
{
amount = value;
RaisePropertyChangedEvent("Amount");
}
}
private string notes;
public string Notes
{
get { return notes; }
set
{
notes = value;
RaisePropertyChangedEvent("Notes");
}
}
#endregion
public void DeleteTransactionRow(List<TestModel> SelectedTransaction, int SelectedIndex)
{
Transactions.RemoveAt(SelectedIndex);
}
View:
<Window x:Class="OCTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:Properties="clr-namespace:OCTest.Properties"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:xcdg="http://schemas.xceed.com/wpf/xaml/datagrid"
Title="Test" SizeToContent="WidthAndHeight"
xmlns:ViewModel="clr-namespace:OCTest.ViewModel">
<Window.DataContext>
<ViewModel:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<xcdg:DataGridControl x:Name="TransactionsDataGrid" Grid.Row="0" ItemsSource="{Binding Transactions, Mode=TwoWay}" AutoCreateColumns="False" SelectionMode="Single">
<xcdg:DataGridControl.ContextMenu>
<ContextMenu>
<MenuItem Header="Delete Row" Click="DeleteTransactionRow_Click"/>
</ContextMenu>
</xcdg:DataGridControl.ContextMenu>
<xcdg:DataGridControl.Columns>
<xcdg:Column Title="Type" FieldName="Type" ReadOnly="True"/>
<xcdg:Column Title="Amount" FieldName="Amount">
<xcdg:Column.CellContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding StringFormat={}{0:C}}"/>
</DataTemplate>
</xcdg:Column.CellContentTemplate>
</xcdg:Column>
<xcdg:Column Title="Notes" FieldName="Notes"/>
</xcdg:DataGridControl.Columns>
</xcdg:DataGridControl>
</Grid>
Code behind that deals with the delete command and passes needed information to the view model:
public partial class MainWindow : Window
{
MainWindowViewModel mainwindowviewmodel = new MainWindowViewModel();
public MainWindow()
{
InitializeComponent();
}
private void DeleteTransactionRow_Click(object sender, RoutedEventArgs e)
{
List<TestModel> selectedtransaction = TransactionsDataGrid.SelectedItems.Cast<TestModel>().ToList();
mainwindowviewmodel.DeleteTransactionRow(selectedtransaction, TransactionsDataGrid.SelectedIndex);
}
private void MouseRightButtonUpHandler(object sender, RoutedEventArgs e)
{
this.TransactionsDataGrid.SelectedItem = ((DataCell)sender).ParentRow.DataContext;
}
}
So hopefully someone can see why the RemoveAt doesn't update the view.
You need to set the DataContext of the MainWindow to your view model:
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
I'd also recommend looking into binding click events to commands in your view model rather than relying on the code behind.
I have a viewmodel named AllUserViewModel that contains a list of UserViewModels.
The UserViewModel is binded with a UserControl.
This is my MainWindow, foreach user I add a UserControl to a stackpanel container.
public partial class MainWindow : Window
{
public AllUserViewModel allUserViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
DwmDropShadow.DropShadowToWindow(this);
allUserViewModel = new AllUserViewModel();
this.DataContext = allUserViewModel;
allUserViewModel.Users.Add(new UserViewModel(new User(1, "Robby", "Bezet")));
allUserViewModel.Users.Add(new UserViewModel(new User(2, "Erwin", "Bezet")));
allUserViewModel.Users.Add(new UserViewModel(new User(3, "Kevin", "Bezet")));
foreach (UserViewModel u in allUserViewModel.Users)
{
Container.Children.Add(new UserControlButton(u));
}
Container.MouseEnter += new MouseEventHandler(Container_MouseEnter);
}
void Container_MouseEnter(object sender, MouseEventArgs e)
{
UserViewModel u = allUserViewModel.GetUser(2);
u.Name = "Laurens"; // Doesn't work
}
}
To each UserControlButton I pass a UserViewModel
public partial class UserControlButton : UserControl
{
public UserViewModel userViewModel { get; set; }
public UserControlButton(UserViewModel u)
{
this.InitializeComponent();
this.DataContext = u;
}
}
And this is my UserControlButton
<UserControl
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:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:ee="http://schemas.microsoft.com/expression/2010/effects"
mc:Ignorable="d"
x:Class="UserControlSolution.UserControlButton"
x:Name="UserControl"
Height="50" Background="#FF2F2F2F"
VerticalAlignment = "Top"
Margin="2,0,0,5"
>
<StackPanel x:Name="UserContainer" Orientation="Vertical" VerticalAlignment="Center" Grid.Column="1" Background="{DynamicResource DarkGrey}">
<TextBlock x:Name="NameLabel" FontSize="16" Foreground="#FFE5E5E5" Text="{Binding Name}" VerticalAlignment="Top" FontFamily="Segoe UI Semibold" RenderTransformOrigin="0.5,0.5" Margin="0,0,0,2"/>
<TextBlock x:Name="UserStatusLabel" Text="{Binding UserStatus}" TextWrapping="NoWrap" VerticalAlignment="Top" Foreground="#FFE5E5E5" />
</StackPanel>
</UserControl>
AllUserViewmodel
public class AllUserViewModel : BaseViewModel
{
ObservableCollection<UserViewModel> _users;
public AllUserViewModel()
{
Users = new ObservableCollection<UserViewModel>();
}
/// <summary>
/// Observable Collection of Users
/// Uses INotifyPropertyChange when list changes
/// </summary>
public ObservableCollection<UserViewModel> Users
{
get { return _users; }
set
{
if (_users != value)
{
_users = value;
NotifyPropertyChanged("Users");
}
}
}
public void AddUser(User user)
{
UserViewModel userViewModel = new UserViewModel(user);
Users.Add(userViewModel);
}
public UserViewModel GetUser(int ID)
{
foreach (UserViewModel u in Users)
{
if(u.ID == ID)
return u;
}
return null;
}
}
And UserViewModel
public class UserViewModel : BaseViewModel
{
readonly User _user;
public UserViewModel(User user)
{
if (user == null)
throw new ArgumentNullException("User");
_user = user;
}
public string Name
{
get { return _user.Name; }
set
{
if (value == _user.Name)
return;
_user.Name = value;
NotifyPropertyChanged("UserName");
}
}
public string UserStatus
{
get { return _user.UserStatus; }
set
{
if (value == _user.UserStatus)
return;
_user.UserStatus = value;
NotifyPropertyChanged("UserStatus");
}
}
public int ID
{
get { return _user.ID; }
}
}
The problem is that the 3 users are shown initially, but when I try to change the name on the mouse enter event, the name is not changed although the NotifyPropertyChanged was triggered.
public string Name
{
get { return _user.Name; }
set
{
if (value == _user.Name)
return;
_user.Name = value;
NotifyPropertyChanged("UserName");
}
}
Your property is called Name but you raise a PropertyChanged event for a property called UserName !