Hi I have a beginner problem. I have shell (it is wpf window) and in this shell is screen (it is an user control / view model).
I would like open new window from view model, not show user control in shell.
So I create new window - ChatView
<Window x:Class="Spirit.Views.ChatView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:extToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit.Extended" Title="ChatView" Height="545" Width="763">
<Grid Margin="4,4,4,4">
</Grid>
</Window>
Export ChatViewModel with MEF.
public interface IChatViewModel
{
}
[Export("ChatScreen",typeof(IChatViewModel))]
public class ChatViewModel
{
}
In view model I have this method:
With ShowScreen class help me Mr.Marco Amendola. It look likes this:
public class ShowScreen : IResult
{
readonly Type _screenType;
readonly string _name;
[Import]
public IShellViewModel Shell { get; set; }
Action<object> _initializationAction = screen => { };
public ShowScreen InitializeWith<T>(T argument)
{
_initializationAction = screen =>
{
var initializable = screen as IInitializable<T>;
if (initializable != null)
initializable.Initialize(argument);
};
return this;
}
public ShowScreen(string name)
{
_name = name;
}
public ShowScreen(Type screenType)
{
_screenType = screenType;
}
public void Execute(ActionExecutionContext context)
{
var screen = !string.IsNullOrEmpty(_name)
? IoC.Get<object>(_name)
: IoC.GetInstance(_screenType, null);
_initializationAction(screen);
Shell.ActivateItem(screen);
Completed(this, new ResultCompletionEventArgs());
}
public event EventHandler<ResultCompletionEventArgs> Completed = delegate { };
public static ShowScreen Of<T>()
{
return new ShowScreen(typeof(T));
}
}
My problem is if I try show new window it doesn’t works, it works only if I show new user control in shell(window).
I would like achieve behavior something like in skype. You have a main window with listbox, you double clicked on item and it show new chat window.
Main window can publish with EventAggregator on chat window and also chat window can publish on main window. This is my goal.
I know that I can not use class ShowScreen on showing new Window. I would like to know what is correct way to create new window from view model and inject event aggregator
to this vie model.
Any advice? Thank for your help and time.
Have you looked at WindowManager.Show or WindowManager.ShowDialog? Rob has a sample at http://caliburnmicro.codeplex.com/wikipage?title=The%20Window%20Manager. You can inject this dependency into your view model as IWindowManager.
I'm using this. Maybe could save a question about "where's the code ?".
DialogHelper:
public class DialogHelper
{
public void ShowDialog<T>(params Object[] param) where T : class
{
var windowManager = new WindowManager();
T viewModel = Activator.CreateInstance(typeof(T), param) as T;
windowManager.ShowWindow(viewModel);
}
}
How to use:
Without constructor parameter:
Dialog.ShowDialog<TestTableViewModel>();
With constructor paramater:
Dialog.ShowDialog<TestTableViewModel>(dt);
Note that I'm not using MEF
Related
I encountered a very strange issue and cannot understand how to solve it.
My application window has a page navigator on the right. When you click any page in the navigator this page is displayed on the main preview surface on the left. Here how it looks like:
In the background there are 3 ViewModels: Root VM for the main window, Navigator VM and Page VM.
The Navigator VM looks as follows:
public class PageNavigatorViewModel : ReactiveObject, IPageNavigatorViewModel
{
public PageNavigatorViewModel()
{
Pages = new ObservableCollectionExtended<IPageViewModel>();
AddEmptyPageCommand = ReactiveCommand.Create(AddEmptyPage);
SelectPageCommand = ReactiveCommand.Create(SelectPage);
}
public IObservableCollection<IPageViewModel> Pages { get; }
// Is bound to a button in UI:
public ReactiveCommand<Unit, Unit> AddEmptyPageCommand { get; }
// This command is executed when the user clicks on a page in the Page Navigation Panel:
public ReactiveCommand<IPageViewModel, Unit> SelectPageCommand { get; }
public IPageViewModel CurrentPage
{
get => _CurrentPage;
set => SetCurrentPage(value);
}
public void AddEmptyPage()
{
var page = CreatePage();
Pages.Add(page);
SetCurrentPage(page);
}
public void SelectPage(IPageViewModel page)
{
SetCurrentPage(page);
}
private IPageViewModel _CurrentPage;
private void SetCurrentPage(IPageViewModel page)
{
foreach (var p in Pages)
p.IsCurrent = false;
page.IsCurrent = true;
this.RaiseAndSetIfChanged(ref _CurrentPage, page, nameof(CurrentPage));
}
}
The VM of the main window is as follows:
public class MainViewModel : ReactiveObject, IMainViewModel
{
public RootViewModel()
{
PageNavigator = new PageNavigatorViewModel();
this.WhenPropertyChanged(vm => vm.PageNavigator.CurrentPage)
.Subscribe(vm => CurrentPage = vm.Value);
}
public IPageNavigatorViewModel PageNavigator { get; protected set; }
[Reactive] public IPageViewModel CurrentPage { get; protected set; }
}
And finally here the code in the main window:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
Initialize();
}
private void Initialize()
{
ViewModel = new RootViewModel();
// Add a few pages with random background:
for (var i = 0; i < 5; i++)
{
ViewModel.PageNavigator.AddEmptyPage();
ViewModel.PageNavigator[i].Background = GetRandomColor();
}
this.WhenActivated(d =>
{
// DrawingSurfaceBorder - it's ... just a standard WPF Border control:
this.OneWayBind(ViewModel, vm => vm.CurrentPage.Background, v => v.DrawingSurfaceBorder.Background).DisposeWith(d);
});
}
}
So, everything works as expected for those 5 pages added on VM initialization: I click a page and can see that the preview changes its color to the selected page. But when I add a new page, and in the code you can see this new page is Current now, however, the preview does not change at all, it's just ignores new added pages. Looks like the ReactiveUI does not bind new pages to the view. And I could not find anything on this issue on the web.
Wow, I've just found the answer accidentally! AddEmptyPage does not set the PageViewModel.Background property, so it is just null for all new pages. Looks like this breaks the binding, because setting this property fixed the issue. It is really hard to track mistake. I would be happy to have some exception on such case, but it's WPF - the most hard to debug framework in the Universe. I think I would kill myself even before my boss would lose his patience XD
I have a WPF project that is part of the solution, it is "ProjectFilesSelector". A some project, named A. call ProjectFilesSelector as figure below:
!!! UPDATED:
namespace ProjectFilesSelector
{
...
public class ViewModel
{
...
public ICommand cancel
{
get
{
return new WPFExtensions.RelayCommand(_ =>
{
this.window.Visibility = Visibility.Hidden;
this.window.Close();
});
}
}
}
public partial class Window1 : Window, IDisposable
{
public Window1(ProjectTypes.Project pro)
{
InitializeComponent();
var context = new ViewModel(this, new ATChecker.ViewModel.ProjectModel(pro));
this.DataContext = context;
}
...
}
}
namespace ATCheckerView
{
public class ViewerClientExt : INotifyPropertyChanged
{
...
public ICommand CheckPrinciplies
{
get
{
var cmnd =
new RelayCommand(project =>
{
var proj = (ViewModel.ProjectModel)project;
ProjectFilesSelector.ViewModel dc;
using (var a = new ProjectFilesSelector.Window1(proj.project))
{
a.ShowDialog(); // cancel command was called
dc = (ProjectFilesSelector.ViewModel)a.DataContext;
}
....
// some code
// and I can still see the window of Window1. Why?
});
I don t think you want to exit the application. You may just want to close the window.
The cancel button just set IsCancel to true. And for the ok button the best way is to create a event in the viewmodel to get up to the view.
I haveViewModel1 and View1 associated with it. I start dialog window from ViewModel2 (some another viewmodel) using IWindowManager object. The code from ViewModel2 class:
windowManager.ShowDialog(new ViewModel());
So, I have Dialog Window with View1 user control.
My answer is next - I can close that dialog window using red close button, but how to close it using my specific button (contained in View1 user control), something like "Cancel" button with close command (Command={Binding CancelCommand}), CancelCommand of course is contained in ViewModel1 class.
It's even easier if your view model extends Caliburn.Micro.Screen:
TryClose();
You can get the current view (in your case the dialog window) with implementing the IViewAware interface on your ViewModel. Then you can call Close on the the view (the Window created as the dialog) when your command is executed.
The easiest why is to derive from ViewAware:
public class DialogViewModel : ViewAware
{
public void ExecuteCancelCommand()
{
(GetView() as Window).Close();
}
}
If you are not allowed to derive you can implement it yourself:
public class DialogViewModel : IViewAware
{
public void ExecuteCancelCommand()
{
dialogWindow.Close();
}
private Window dialogWindow;
public void AttachView(object view, object context = null)
{
dialogWindow = view as Window;
if (ViewAttached != null)
ViewAttached(this,
new ViewAttachedEventArgs(){Context = context, View = view});
}
public object GetView(object context = null)
{
return dialogWindow;
}
public event EventHandler<ViewAttachedEventArgs> ViewAttached;
}
Note: I've used Caliburn.Micro 1.3.1 for my sample.
A cleaner way (Subject of personal taste) that I use alot is to use the IResult pattern, this way you abstract the Window implemenation
Viewmodel
public IEnumerable<IResult> CloseMe()
{
yield return new CloseResult();
}
Result code
public class CloseResult : Result
{
public override void Execute(ActionExecutionContext context)
{
var window = Window.GetWindow(context.View);
window.Close();
base.Execute(context);
}
}
public abstract class Result : IResult
{
public virtual void Execute(ActionExecutionContext context)
{
OnCompleted(this, new ResultCompletionEventArgs());
}
protected virtual void OnCompleted(object sender, ResultCompletionEventArgs e)
{
if (Completed != null)
Completed(sender, e);
}
public event EventHandler<ResultCompletionEventArgs> Completed;
}
edit (Only needed for IoC): If you wanna take it a step further you do a base class for all screens
public abstract class ShellPresentationModel : Screen
{
public ShellPresentationModel(IResultFactory resultFactory)
{
Result = resultFactory;
}
public IResultFactory Result { get; private set; }
}
This way you can inject dependencies with a IoC much easier, then your VIewmodel close method will look like this
public IEnumerable<IResult> CloseMe()
{
yield return Result.Close();
}
An example on a IResult that uses dependency can be
public class ShowDialogResult<TModel> : Result
{
private readonly IWindowManager windowManager;
private readonly TModel model;
private Action<TModel> configure;
public ShowDialogResult(IWindowManager windowManager, TModel model)
{
this.windowManager = windowManager;
this.model = model;
}
public IResult Configure(Action<TModel> configure)
{
this.configure = configure;
return this;
}
public override void Execute(ActionExecutionContext context)
{
if(configure != null)
configure(model);
windowManager.ShowDialog(model);
base.Execute(context);
}
}
edit Just noticed that i forgot to add an example of the above IoC exmaple, here goes
With a child IoC container pattern it would look like this
public IEnumerable<IResult> ShowDialog()
{
yield return Result.ShowDialog<MyViewModel>();
}
Without a child container pattern you would need to inject parent dependeync into the child manually
yield return Result.ShowDialog<MyViewModel>().Configure(m => m.SomeData = this.SomeData);
I have a WinForm dialog and I want to set its Parent property to a WPF window.
How can I do this?
Consider passing parameter to ShowDialog method instead of using Parent property.
You can write helper class
class Wpf32Window : IWin32Window
{
public IntPtr Handle { get; private set; }
public Wpf32Window(Window wpfWindow)
{
Handle = new WindowInteropHelper(wpfWindow).Handle;
}
}
public static class WindowExtensions
{
public static IWin32Window GetWin32Window (this Window parent)
{
return new Wpf32Window(parent);
}
}
After that you can just write
winFormsWindow.Show(yourWpfWindow.GetWin32Window());
I have an ItemType that is coming from EF. This ItemType is wrapped in a ItemTypeViewModel. Many ItemTypes are wrapped in ItemTypeViewModels and are being put in a ObservableCollection in the ViewModel for the user control that will display them:
I use the CollectionView so I can page through them. The screen looks like this:
Now I'm thinking that the buttons that are used for paging that are in the user control could better be placed in the Window that will contain the user control. So, in my user control I know have commands like this:
But I want them to be in the window. I don't know if this will be good design, but if I will go through with this, how to relay the commands from the window to the usercontrol?
Another question I have is how to fill the combobox in the user control. They will always have the same values, but the selected item will change per ItemType.
I know two ways how to do this.
1) Add new class, for example, MainWindowViewModel and add there 2 commands and an instance of UserControlViewModel (you haven't said the title, so I will call it in this way). Here is a part of example:
public class MainWindowViewModel
{
public UserControlViewModel ChildControlViewModel { get; set; }
private Lazy<RelayCommand> nextCommand = new Lazy<RelayCommand>(() =>
new RelayCommand(
() => this.ChildControlViewModel.CollectionView.MoveCurrentToNext(),
() => this.ChildControlViewModel.CollectionView.CurrentPosition < this.ChildControlViewModel.ItemTypes.Count - 1));
public ICommand NextCommand
{
get { return nextCommand.Value; }
}
//prev command...
}
I have used the Lazy class, but the main idea is clear: the code is the same, except the call this.ChildControlViewModel.CollectionView instead of CollectionView.
2) Use the Messenger class.
This way isn't so obvious and it has only one advantage: the viewmodels are loosely connected.
public class MainWindowViewModel
{
public const string NextCommandNotification = "NextCommand";
public const string PreviousCommandNotification = "PreviousCommand";
private bool isNextCommandEnabled;
private bool isPreviousCommandEnabled;
public MainWindowViewModel()
{
this.NextCommand = new RelayCommand(
() => Messenger.Default.Send(new NotificationMessage<MainWindowViewModel>(this, NextCommandNotification)),
() => this.isNextCommandEnabled);
//prev command...
Messenger.Default.Register<NotificationMessage<UserControlViewModel>>(this,
msg =>
{
if (msg.Notification == UserControlViewModel.CurrentItemChangedNotification)
{
this.isNextCommandEnabled = msg.Content.CollectionView.CurrentPosition < msg.Content.ItemTypes.Count - 1;
this.NextCommand.RaiseCanExecuteChanged();
//prev command...
}
});
}
public ICommand NextCommand { get; private set; }
//prev command...
}
public class UserControlViewModel
{
public const string CurrentItemChangedNotification = "CurrentItemChanged";
public UserControlViewModel()
{
Messenger.Default.Register<NotificationMessage<MainWindowViewModel>>(this,
msg =>
{
if (msg.Notification == MainWindowViewModel.NextCommandNotification)
this.CollectionView.MoveCurrentToNext();
else if (msg.Notification == MainWindowViewModel.PreviousCommandNotification)
this.CollectionView.MoveCurrentToPrevious();
});
this.CollectionView.CurrentChanged += (s,e) => Messenger.Default.Send(new NotificationMessage<UserControlViewModel>(this, CurrentItemChangedNotification))
}
}
I'm not sure whether this code will work correctly. And it is not easy to explain.
The MainWindowViewModel class send the message when a user press the button. The UserControlViewModel class process the message, change the position of the current item, and send the CurrentItemChangedNotification message. The MainWindowViewModel class process this message and updates the CanExecute part of the command.
1st solution is better for me, but at the same time I use the Messenger class quite often. It depends on the situation.