WPF MVVM view redirection from Viewmodel - wpf

Am working with WPF MVVM,
I have a main window and few usercontrols.based on the button click am showing the view and viewmodels. When i click button in main window i can navigate to different view. because navigation command is in mainwindow viewmodel only. But when am in different view(user control) . How can i navigate from viewmodel.
MainwindowViewmodel
public class MainWindowViewModel : BindableBase, INotifyPropertyChanged
{
private StudyViewModel _studyViewModel = new StudyViewModel();
private ImageCaptureViewModel _imageCaptureViewModel = new ImageCaptureViewModel();
private RegisterViewModel _registerViewModel = new RegisterViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel
{
get { return _CurrentViewModel; }
set { SetProperty(ref _CurrentViewModel, value); OnPropertyChanged("_CurrentViewModel"); }
}
public MainWindowViewModel()
{
NavCommand = new RelayCommand<string>(onNav);
onNav("study");
}
//This is the command am using in xaml to redirect view.
public RelayCommand<string> NavCommand { get; private set; }
private void onNav(string destination)
{
switch (destination)
{
case "study":
CurrentViewModel = _studyViewModel;
break;
case "capture":
CurrentViewModel = _imageCaptureViewModel;
break;
case "register":
CurrentViewModel = _registerViewModel;
break;
default:
CurrentViewModel = _studyViewModel;
break;
}
}
}
RegisterViewModel
public void RegisterPatient(string action)
{
if (action == "addnew")
{
}
else
{
if (StartImaging)
{
//Have to redirect to other screen.
}
}
var a = PatientToAdd;
MessageBox.Show("triggered");
}
When am adding command in mouse event it is redirecting .But i dont no how to redirect from viewmodel
RegisterView.xaml
<DataGrid.InputBindings>
<MouseBinding Command="{Binding DataContext.NavCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
MouseAction="LeftDoubleClick"
CommandParameter="historyimage"/>
</DataGrid.InputBindings>

Your question is not clear enough, so this is based on what I think you need.
MainWindow:
<Window x:Class="MyApp.MainWindow" ...>
...
<ContentControl Content={Binding MyCurrentViewModel}>
<ContentControl.Resources>
<DataTemplate DataType={x:Type vm:UserListViewModel}>
<view:UserListView />
</DataTemplate>
<DataTemplate DataType={x:Type vm:AnotherViewModel}>
<view:AnotherView />
</DataTemplate>
...
</ContentControl.Resources>
</ContentControl>
</Window>
MainViewModel:
private ViewModelBase _myCurrentViewModel;
public ViewModelBase MyCurrentViewModel
{
get { return _myCurrentViewModel; }
set
{
if (value != _myCurrentViewModel)
{
_myCurrentViewModel = value;
RaisePropertyChanged("MyCurrentViewModel");
}
}
}
UserListViewModel:
public class UserListViewModel : ViewModelBase
{
private MainViewModel MainVM;
public UserListViewModel(MainViewModel mainVM)
{
this.MainVM = mainVM;
}
private ICommand _myCommand;
public ICommand MyCommand
{
get
{
if (_myCommand = null)
_myCommand = new RelayCommand(MyExecuteDelegate, true);
return _myCommand;
}
}
private void MyExecuteDelegate(object parameter)
{
// My other logic
if (this.MainVM != null)
this.MainVM.MyCurrentViewModel = new AnotherViewModel();
}
}

Related

Can I connect two ViewModels without DependencyProperty?

I have MainWindow (simplified for clarity):
<Window>
<!-- (...) -->
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<!-- (...) -->
<CheckBox IsChecked="{Binding ShowAdvanced}" Content="Advanced view" />
<uc:MyUserControl DataContext={Binding MyUserControlViewModel} />
</Window>
MainWindowViewModel:
public partial class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
MyUserControlVM = new MyUserControlViewModel();
}
private bool _showAdvanced;
public bool ShowAdvanced
{
get => _showAdvanced;
set { _showAdvanced = value; NotifyPropertyChanged(); }
}
private MyUserControlViewModel _myUserControlVM;
public MyUserControlViewModel MyUserControlVM
{
get => _myUserControlVM;
set { _myUserControlVM= value; NotifyPropertyChanged(); }
}
}
In my UserControl I have some controls supposed to be hidden when "Show advanced" checkbox is not checked.
<GroupBox Header="Some advanced stuff"
Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}, Path=DataContext.(vm:MainWindowViewModel.ShowAdvanced), Converter={StaticResource BoolToVis}}">
<!-- (...) -->
</GroupBox>
This actually works, but I don't like this because UserControl relies on MainWindow.
How can I connect these viewmodels correctly without DependencyProperty?
I have tried to add this to MyUserControlViewModel:
public MyUserControlViewModel(MainWindowViewModel parent)
{
Parent = parent;
}
private MainWindowViewModel _parent;
public MainWindowViewModel Parent
{
get { return _parent; }
set { _parent = value; NotifyPropertyChanged(); }
}
and bind visibility on one of MyUserControl controls like this:
Visibility="{Binding Parent.ShowAdvanced}"
but this is not working (MyUserControl is not getting notified?).
Add the ShowAdvanced property to the control VM and assign the value to each control VM whenever a new value is assigned to the MainViewModel ShowAdvanced property.
public class MainViewModel : Base.ViewModelBase
{
private bool _showAdvanced;
public MainViewModel()
{
MyUserControl1 = new MyUserControlViewModel { Message = "Control 1" };
MyUserControl2 = new MyUserControlViewModel { Message = "Control 2" };
MyUserControl3 = new MyUserControlViewModel { Message = "Control 3" };
}
public bool ShowAdvanced
{
get => _showAdvanced;
set
{
this.RaiseAndSetIfChanged( ref _showAdvanced, value );
MyUserControl1.ShowAdvanced = value;
MyUserControl2.ShowAdvanced = value;
MyUserControl3.ShowAdvanced = value;
}
}
public MyUserControlViewModel MyUserControl1 { get; }
public MyUserControlViewModel MyUserControl2 { get; }
public MyUserControlViewModel MyUserControl3 { get; }
}
public class MyUserControlViewModel : Base.ViewModelBase
{
private bool _showAdvanced;
private string _message;
public bool ShowAdvanced { get => _showAdvanced; set => this.RaiseAndSetIfChanged( ref _showAdvanced, value ); }
public string Message { get => _message; set => this.RaiseAndSetIfChanged( ref _message, value ); }
}

WPF: implement MVVM button command

So i have WPF application with main windoes and 2 UserControls:
HomeView.xaml
OptionsView.xaml
View Model
public class ApplicationViewModel : INotifyPropertyChanged
{
#region Fields
private ICommand changePageCommand;
private ICommand addFilesCommand;
private IPageViewModel _currentPageViewModel;
private List<IPageViewModel> _pageViewModels;
#endregion
public ApplicationViewModel()
{
// Add available pages
PageViewModels.Add(new HomeViewModel() { IsSelected = true });
PageViewModels.Add(new OptionsViewModel() { IsSelected = false });
// Set starting page
CurrentPageViewModel = PageViewModels[0];
}
#region Properties / Commands
public ICommand ChangePageCommand
{
get
{
if (changePageCommand == null)
{
changePageCommand = new RelayCommand(
p => ChangeViewModel((IPageViewModel)p),
p => p is IPageViewModel);
}
return changePageCommand;
}
}
public List<IPageViewModel> PageViewModels
{
get
{
if (_pageViewModels == null)
_pageViewModels = new List<IPageViewModel>();
return _pageViewModels;
}
}
public IPageViewModel CurrentPageViewModel
{
get
{
return _currentPageViewModel;
}
set
{
if (_currentPageViewModel != value)
{
_currentPageViewModel = value;
OnPropertyChanged("CurrentPageViewModel");
}
}
}
#endregion
#region Methods
private void ChangeViewModel(IPageViewModel viewModel)
{
if (!PageViewModels.Contains(viewModel))
PageViewModels.Add(viewModel);
CurrentPageViewModel = PageViewModels.FirstOrDefault(vm => vm == viewModel);
}
#endregion
}
Whan application start
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow app = new MainWindow();
ApplicationViewModel context = new ApplicationViewModel();
app.DataContext = context;
app.Show();
}
}
Windows respurces
<Window.Resources>
<DataTemplate DataType="{x:Type home:HomeViewModel}">
<home:HomeView />
</DataTemplate>
<DataTemplate DataType="{x:Type options:OptionsViewModel}">
<options:OptionsView />
</DataTemplate>
</Window.Resources>
And inside HomeView.xaml i have simple button:
<Button Command="{Binding DataContext.AddFilesCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding}"/>
And i want to add simple Click command, something.
So i try to add this ICommand:
public ICommand AddFilesCommand
{
}
Any suggestions how to add this kind on command that will execute after each Button Click ?
This can be done a lot easier. I would create a class to implement commands easily:
using System;
using System.Windows.Input;
namespace YourNameSpace
{
public class RelayCommand : ICommand
{
private Action Action;
public Command(Action _action)
{
Action = _action;
}
public event EventHandler CanExecuteChanged = (sender, e) => { };
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
Action();
}
}
}
Then create the command (you don't need private ICommand for this):
public ICommand AddFileCommand { get; set; }
And use it like this (in the constructor):
AddFileCommand = new RelayCommand(()=>
{
MethodToExecute();
});
XAML:
<Button Command="{Binding AddFileCommand}"/>
This way your code will be easier to see trough.

WPF: when move to other page all my ListView data disappear

I have WPF application with 2 pages and this Frame:
<Window>
<Window.DataContext>
<common:ViewModel/>
</Window.DataContext>
<Grid Name="GridMain" Grid.Row="1">
<Frame Name="MyFrame"
NavigationUIVisibility="Hidden"
Source="Pages/home.xaml"/>
</Grid>
</Window>
So i have 2 Pages
Home.xaml
Options.xaml
And my ViewModel:
public class ViewModelBase : INotifyPropertyChanged
{
private ObservableCollection<MyData> files;
public ICommand SliderSpeedValueChangedCommand { get; set; }
public ViewModelBase()
{
Files = new ObservableCollection<MyData>();
SliderSpeedValueChangedCommand = new SliderSpeedValueChangedCommand(this);
}
public ObservableCollection<MyData> Files
{
get { return files; }
set
{
files = value;
NotifyPropertyChanged();
}
}
}
So in my HomePage i have this ListView:
<ListView Name="ListViewFiles"
ItemsSource="{Binding Files}"/>
And the user select files that added into this ListView.
Now when the user switch to the other Page (Options.xaml) and then switch back to HomePage all the files inside this ListView disappear.
Inside this 2 Pages i have also this:
<Page.DataContext>
<viewmodel:ViewModelBase/>
</Page.DataContext>
<Page.Resources>
<vm:ViewModelBase x:Key="ViewModelBase"/>
</Page.Resources>
What i am doing wrong ?
Update
This is how i am switch between Pages:
public partial class MainWindow : Window
{
public ViewModel viewModel;
private Home home;
private Options options;
public MainWindow()
{
InitializeComponent();
viewModel = new ViewModel();
home = new Home();
options = new Options();
}
private void ListBoxMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int index = ListBoxMenu.SelectedIndex;
switch (index)
{
case 0:
MyFrame.Content = home;
break;
case 1:
MyFrame.Content = options;
break;
}
}
}
EDIT
So i have simple command:
public class AddFilesCommand : ICommand
{
public event EventHandler CanExecuteChanged;
public ViewModelBase ViewModel { get; set; }
public AddFilesCommand(ViewModelBase viewModel)
{
ViewModel = viewModel;
}
public bool CanExecute(object parameter)
{
return !ViewModel.AddingFiles;
}
public void Execute(object parameter)
{
ViewModel.AddFile();
}
}
And inside ViewModelBase c'tor i am initiate this class:
AddFilesCommand = new AddFilesCommand(this);
And simple Button:
<Button Command="{Binding AddFilesCommand}"/>
And after click this Button the function Execute not called.
When you write the following in your XAML, it creates a new instance of the ViewModel class and sets the DataContext property to it:
<Window.DataContext>
<common:ViewModel/>
</Window.DataContext>
Since you are creating a ViewModel programmatically, you should get rid of this markup. Create a single instance of a view model for each view and only do this one, e.g.:
public partial class MainWindow : Window
{
private readonly Home home;
private readonly Options options;
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
home = new Home() { DataContext = new ViewModelBase() };
options = new Options() { DataContext = new ViewModelBase() };
}
private void ListBoxMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
int index = ListBoxMenu.SelectedIndex;
switch (index)
{
case 0:
MyFrame.Content = home;
break;
case 1:
MyFrame.Content = options;
break;
}
}
}
And remove this stuff:
<Page.DataContext>
<viewmodel:ViewModelBase/>
</Page.DataContext>
<Page.Resources>
<vm:ViewModelBase x:Key="ViewModelBase"/>
</Page.Resources>

"Shift+enter" should move to new line in WPF application

I am trying to develop a messaging application in WPF..
Now,what I want is when a user clicks on "Enter" the message has to send and when the user clicks "Shift+enter" it should move to a new line.
I have tried something like this,But it doesn't seems to work
if (e.Key == Key.Enter && (e.KeyboardDevice.Modifiers & ModifierKeys.Shift) != ModifierKeys.Shift)
{
//insert newline
}
else if(e.Key==Key.Enter)
{
//Send Message
}
I am using Textbox here.
Set the AcceptsReturn property to true and handle PreviewKeyDown:
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter && Keyboard.Modifiers != ModifierKeys.Shift)
{
//TODO: send message...
e.Handled = true;
}
}
XAML:
<TextBox AcceptsReturn="True" PreviewKeyDown="TextBox_PreviewKeyDown" />
Working on a similar concept. Here is what I did. The below solution also somewhat adheres to MVVM architectural pattern if that's your thing.
You'll need the following.
Step 1:
Add the following for you XAML.
<TextBox Text="{Binding Path=MessageText, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
AcceptsReturn="False" AcceptsTab="True" TextWrapping="Wrap" SpellCheck.IsEnabled ="True">
<TextBox.Resources>
<Style TargetType="{x:Type Border}">
<Setter Property="CornerRadius" Value="3"/>
</Style>
</TextBox.Resources>
<TextBox.InputBindings>
<KeyBinding Key="Enter" Command="{Binding SendMessageCommand, Mode=OneWay}" />
<KeyBinding Gesture="Shift+Return" Command="{Binding NewLineCommand, Mode=OneWay}" CommandParameter="{Binding Path=., Mode=OneWay, ElementName=chattext_field}" />
</TextBox.InputBindings>
Step 2 : Create your view model if you don't already have one. In my example, it called AppViewModel.
class AppViewModel : INotifyPropertyChanged
{
private string messageText = string.Empty;
public string MessageText
{
get { return messageText; }
set
{
messageText = value;
NotifyPropertyChanged();
}
}
public ICommand SendMessageCommand { get; private set; }
public ICommand NewLineCommand { get; private set; }
public void Load()
{
NewLineCommand = new CustomCommand(p =>
{
System.Windows.Controls.TextBox txtB = p as System.Windows.Controls.TextBox;
if (txtB == null)
return;
var caretIdx = txtB.CaretIndex;
if (string.IsNullOrEmpty(MessageText))
MessageText += "\r";
else
MessageText = MessageText.Insert(caretIdx, "\r");
txtB.CaretIndex = caretIdx + 1;
});
SendMessageCommand = new CustomCommand(p =>
{
try
{
// your send message code here
}
catch (LogException ex)
{
System.Windows.MessageBox.Show($"Message sending failure.\n{ex}", "Message Center", MessageBoxButton.OK, MessageBoxImage.Error);
}
});
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Step 3 :
When you load your user control/View using the view model. Initialize/Load the view model when the view is ready.
public partial class MyChatControl : UserControl
{
public MyChatControl()
{
InitializeComponent();
this.Loaded += MyChatControl_Loaded;
}
private void MyChatControl_Loaded(object sender, RoutedEventArgs e)
{
try
{
AppViewModel model = new AppViewModel();
model.Load();
this.DataContext = model;
}
catch (LogException ex)
{
MessageBox.Show($"Failed control content load.\n{ex}", "Failed", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
Almost forgot, here is my "CustomCommand" implementation if you don't have one yet. I have a Async version called "CustomAsyncCommand" as well if you need.
// Interface
public interface ICustomCommand : ICommand
{
event EventHandler<object> Executed;
}
// Command Class
public class CustomCommand : ICustomCommand
{
#region Private Fields
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
#endregion
#region Constructor
public CustomCommand(Action<object> execute) : this(execute, null)
{
}
public CustomCommand(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute ?? (x => true);
}
#endregion
#region Public Methods
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter = null)
{
Refresh();
_execute(parameter);
Executed?.Invoke(this, parameter);
Refresh();
}
public void Refresh()
{
CommandManager.InvalidateRequerySuggested();
}
#endregion
#region Events
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public event EventHandler<object> Executed;
#endregion
}
Only Set the AcceptsReturn property to true
XMAL
<TextBox AcceptsReturn="True" />

ListBox Map J and K Keys to Up/Down Arrow Keys

I have a ListBox and I simply want to bind the J and K keys to whatever commands the up and down arrow keys are bound to. The up and down arrow keys in a WPF listbox typically change the selected item to the previous/next item. I thought something like this should work:
<ListBox.InputBindings>
<KeyBinding Key="J" Command="ScrollBar.LineDownCommand" />
<KeyBinding Key="K" Command="ScrollBar.LineUpCommand" />
</ListBox.InputBindings>
I'm probably being too simplistic here.
You can use your DependencyClass on the commands. Define the commands in ListBox.InputBindings:
XAML
<ListBox Name="SampleListBox" Width="200" Height="200" KeyboardNavigation.DirectionalNavigation="Cycle" SelectedIndex="{Binding MySelectedIndex}">
<ListBox.InputBindings>
<KeyBinding Command="{Binding NextCommand}" Gesture="CTRL+J" />
<KeyBinding Command="{Binding PrevCommand}" Gesture="CTRL+K" />
</ListBox.InputBindings>
<ListBoxItem>Sample 1</ListBoxItem>
<ListBoxItem>Sample 2</ListBoxItem>
<ListBoxItem>Sample 3</ListBoxItem>
<ListBoxItem>Sample 4</ListBoxItem>
</ListBox>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// Set your data
this.DataContext = new MainWindowViewModel();
// Set focus
SampleListBox.Focus();
}
}
/// <summary>
/// Class with commands
/// </summary>
public class MainWindowViewModel : DependencyObject
{
public ICommand NextCommand
{
get;
set;
}
public ICommand PrevCommand
{
get;
set;
}
public int MySelectedIndex
{
get
{
return (int)GetValue(MySelectedIndexProperty);
}
set
{
SetValue(MySelectedIndexProperty, value);
}
}
public static readonly DependencyProperty MySelectedIndexProperty =
DependencyProperty.Register("MySelectedIndex", typeof(int), typeof(MainWindowViewModel), new UIPropertyMetadata(0));
public MainWindowViewModel()
{
MySelectedIndex = 0;
NextCommand = new SimpleCommand(SetNext);
PrevCommand = new SimpleCommand(SetPrev);
}
private void SetNext()
{
MySelectedIndex += 1;
}
private void SetPrev()
{
if (MySelectedIndex > 0)
{
MySelectedIndex -= 1;
}
}
}
public class SimpleCommand : ICommand
{
private Action _action;
public SimpleCommand(Action p_action)
{
_action = p_action;
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (_action != null)
{
_action();
}
}
}
In the class contains two ICommand's: NextCommand and PrevCommand. Also there is a DependencyProperty MySelectedIndex, which contains the current index of the item. In SimpleCommand always return true.
This is just an example that still need to check the total number of Items ListBox. Or instead of increasing the SelectedIndex, use ScrollViewer logic.
Extension
Example with ScrollViewer:
To scroll through the items in the ListBox, you must first have access to it. Below is the corresponding function:
public static DependencyObject GetScrollViewer(DependencyObject Object)
{
if (Object is ScrollViewer)
{
return Object;
}
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(Object); i++)
{
var child = VisualTreeHelper.GetChild(Object, i);
var result = GetScrollViewer(child);
if (result == null)
{
continue;
}
else
{
return result;
}
}
return null;
}
Simple function scrolling:
private void OnScrollDown(object sender, RoutedEventArgs e)
{
if (MyListBox.Items.Count > 0)
{
// Get ScrollViewer from ListBox
ScrollViewer scrollViewer = GetScrollViewer(MyListBox) as ScrollViewer;
if (scrollViewer != null)
{
// Increment offset - scrolling Down, sub - scrolling Up
scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset + ScrollListBoxOffset);
}
}
}

Resources