Mahapps 1.3 dialogs and Avalon.Wizard - c#

I've integrated the popular UI library Mahapps with the Avalon.Wizard control.
It integrates nicely, but I have an issue with the Mahapps dialogs. The Wizard control defines an InitializeCommand to handle the entering on a wizard page.
Apparently the InitializeCommand is triggered before the Dependency Property attached to the View is initialized (DialogParticipation.Register).
This cause the following error:
Context is not registered. Consider using DialogParticipation.Register in XAML to bind in the DataContext.
A sample project that reproduce the issue is available here.
Any suggestion on how to fix this?

The page Xaml isn't created at the initialize command, so you can't use the DialogCoordinator at this point.
Here is a custom interface with a LoadedCommand which can you implement at the ViewModel and call it at the Xaml code behind.
public interface IWizardPageLoadableViewModel
{
ICommand LoadedCommand { get; set; }
}
The ViewModel:
public class LastPageViewModel : WizardPageViewModelBase, IWizardPageLoadableViewModel
{
public LastPageViewModel()
{
Header = "Last Page";
Subtitle = "This is a test project for Mahapps and Avalon.Wizard";
InitializeCommand = new RelayCommand<object>(ExecuteInitialize);
LoadedCommand = new RelayCommand<object>(ExecuteLoaded);
}
public ICommand LoadedCommand { get; set; }
private async void ExecuteInitialize(object parameter)
{
// The Xaml is not created here! so you can't use the DialogCoordinator here.
}
private async void ExecuteLoaded(object parameter)
{
var dialog = DialogCoordinator.Instance;
var settings = new MetroDialogSettings()
{
ColorScheme = MetroDialogColorScheme.Accented
};
await dialog.ShowMessageAsync(this, "Hello World", "This dialog is triggered from Avalon.Wizard LoadedCommand", MessageDialogStyle.Affirmative, settings);
}
}
And the View:
public partial class LastPageView : UserControl
{
public LastPageView()
{
InitializeComponent();
this.Loaded += (sender, args) =>
{
DialogParticipation.SetRegister(this, this.DataContext);
((IWizardPageLoadableViewModel) this.DataContext).LoadedCommand.Execute(this);
};
// if using DialogParticipation on Windows which open / close frequently you will get a
// memory leak unless you unregister. The easiest way to do this is in your Closing/ Unloaded
// event, as so:
//
// DialogParticipation.SetRegister(this, null);
this.Unloaded += (sender, args) => { DialogParticipation.SetRegister(this, null); };
}
}
Hope this helps.

Related

WPF InteractionRequest is always null

Hi I'm new to WPF and XAML, I'm trying to utilize MVVMCross's MvxInteraction to interact with the user to get a "YES" or "NO" confirmation based off this example.
I've been hitting a snag on getting the interaction to subscribe to an event handler as the interaction is always null. I can see that from the references that the interaction variable see each other based on the binding, so I'm not sure what's going on. I've looked around and found this, that states for me to bring my binding later into my UserControl View behind code, so I used a dispatcher, but that did not work either.
VIEW MODEL
public class StudentDetailsViewModel : MvxViewModel
{
private InteractionRequest<YesNoQuestion> _interaction = new InteractionRequest<YesNoQuestion>();
public IInteractionRequest Interaction => _interaction;
}
VIEW.XAML.CS
public partial class StudentDetailsView : MvxWpfView
{
private InteractionRequest<YesNoQuestion> _interaction;
public StudentDetailsView()
{
InitializeComponent();
Dispatcher.BeginInvoke(new Action(() => BindInteractions()), DispatcherPriority.ContextIdle, null);
}
public InteractionRequest<YesNoQuestion> Interaction
{
get => _interaction;
set
{
if(_interaction != null)
{
_interaction.Requested -= OnInteractionRequested;
}
_interaction = value;
_interaction.Requested += OnInteractionRequested; //***RUN TIME NULL EXCEPTION***
}
}
private void OnInteractionRequested(object sender, InteractionRequestedEventArgs eventArgs)
{
var yesNoQuestion = eventArgs.Callback;
}
private void BindInteractions()
{
var set = this.CreateBindingSet<StudentDetailsView, StudentDetailsViewModel>();
set.Bind(this).For(view => view.Interaction).To(viewModel => viewModel.Interaction).OneWay();
set.Apply();
}
}
INTERACTION CLASS
public class YesNoQuestion
{
public bool? Confirmation { get; set; }
public string Question { get; set; }
public YesNoQuestion(string message)
{
Question = message;
}
}
My second question is that I'm a little confused on what they implemented with the "ShowDialog" and "DialogStatus" here within their example:
private async void OnInteractionRequested(object sender, MvxValueEventArgs<YesNoQuestion> eventArgs)
{
var yesNoQuestion = eventArgs.Value;
// show dialog
var status = await ShowDialog(yesNoQuestion.Question);
yesNoQuestion.YesNoCallback(status == DialogStatus.Yes);
}
Are they simply calling upon another usercontrol view to show itself through a ShowDialog Method?
_interaction.Requested += OnInteractionRequested; //***RUN TIME NULL EXCEPTION***
Somehow this is always null on the first startup, and then it will assign the proper interaction later, so add a null check to solve this. Maybe we need to confirm with MVVMCross itself.
Second, you can handle whatever you want to display on interaction request, for example, shows MessageBox with yes no button type or pop another view to display custom message box one. Since this runs on the WPF layer.

MVVMCross How to display a view within a view

I'm a new to the MVVMCross package, and C# for that matter. I've spent the better part of the day trying to figure out what I'm not understanding reading the documentation on presenters and navigation, etc. in order to try to understand, but I'm missing something.
I originally created a WPF app not implementing MVVM and now I wanted to convert, but I'm struggling with this part. I want to have a Main Menu that is part of a grid in a "MainWindow" like shell where the remaining portion of the page (and grid column 2) are used to display a nested view.
Ultimately, I’m just trying to reproduce the same layered controls in the original WPF application. In that app there is a content control Which takes up most of the form whose content property is set to a different form depending on the users selection.
MainWindow.xaml.cs
public partial class MainWindow : MvxWindow
{
public MainWindow(IMvxNavigationService navService)
{
InitializeComponent();
DataContext = new MainViewModel(navService);
//content.Content = new AdminMenuView();
}
}
MainViewModel.cs
private MvxViewModel _nextMenuContent;
public MainViewModel(IMvxNavigationService navService)
{
_navService = navService;
MoveMenuCommand = new MvxCommand(MoveMenu);
ChildViewModel = new AdminMenuViewModel();
GoToAdminMenu = new MvxCommand(SelectAdminMenu);
}
MainView.xaml
<ContentControl Content="{Binding ChildViewModel}"/>
***The grid and columns are all working fine
MainView.xaml.cs
public partial class MainView : MvxWpfView
{
public MainView()
{
InitializeComponent();
}
}
AdminMenuModel.cs
public class AdminMenuViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navService;
public AdminMenuViewModel()
{
Initialize();
}
public override void Prepare()
{
base.Prepare();
}
public override async Task Initialize()
{
await base.Initialize();
}
}
AdminMenuModel.xaml.cs
public partial class AdminMenuView : MvxWpfView
{
public AdminMenuView()
{
InitializeComponent();
}
public new AdminMenuViewModel ViewModel
{
get { return base.ViewModel as AdminMenuViewModel; }
set { base.ViewModel = value; }
}
}
When I call the AdminMenuViewModel it runs, but all I get in the content control is either a blank screen if I Bind the "ChildViewModel" to the DataContext property of the content control and a string of the path to the AdminMenuViewModel if I bind it to the content property.
You have to set MainViewModel as DataContext of your main window
public MainWindow(IMvxNavigationService navService)
{
DataContext = new MainViewModel(navService);
InitializeComponent();
}

Update Observable collection from different UI

I have a WPF application which shows a Folder's contents in a Treeview in the MainWindowView. Now I have a new window where the user can change the location and press reload. Now I want to update the treeview in my MainWindowView as soon as the user presses the Reload button.
I am using an ObservableCollection object which is binded to the treeview. But I am not able to update the collection from the Change location window.
I want to know how to update the ObservableCollection of the MainWindowView from a different window. If I am doing any changes in the MainWindowView, then it immediately reflects in the TreeView
I am using MVVM architecture.
Is there any relationship between the MainWindow and the ChangeLocationWindow?
How does the ChangeLocationWindow show out, Show() or ShowDialog()? Check the following solution, any problems, let me know.
MainWindowViewModel:
public class MainWindowViewModel
{
public static MainWindowViewModel Instance = new MainWindowViewModel();
public ObservableCollection<string> Contents = new ObservableCollection<string>();
public string Location
{
get { return _location; }
set
{
if (_location != value)
{
_location = value;
ReloadContents();
}
}
}
private MainWindowViewModel()
{
}
private void ReloadContents()
{
// fill test data
Contents.Add("Some test data.");
}
private string _location;
}
MainWindowView:
{
public MainWindowView()
{
InitializeComponent();
MyListBox.ItemsSource = MainWindowViewModel.Instance.Contents;
var changeLocationWindow = new ChangeLocationWindow();
changeLocationWindow.Show();
}
}
ChangeLocationWindow:
public partial class ChangeLocationWindow : Window
{
public ChangeLocationWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MainWindowViewModel.Instance.Location = "Test";
}
}
The best approach to your problem is using Messaging pattern to send notifications to main viewmodel from another one about new changes.
Checkout the link for more details,

Using new ViewModel each time I open a page

I didn't know how better to word the title so I went with solution that came to my mind.
Here is the problem. I have a page that has list and each item on the lists opens a detail page (on click). But the VM is reused, which causes me several problems.
Previous data can be seen for split second when opening a the detail page
I need certain properties to be set to specific values when the page open, but since the VM is reused it keeps all the values from the previous detail and this messes up my logic.
This UWP app. I'm using Template10 framework's NavigationService to move between pages.
Main Page ViewModel
public class MainPageViewModel : ViewModelBase {
private List<MangaItem> _mangaList;
public List<MangaItem> mangaList {
get { return _mangaList; }
set { Set(ref _mangaList, value); }
}
private string _mainSearchText;
public string mainSearchText {
get { return _mainSearchText; }
set { Set(ref _mainSearchText, value); }
}
public MainPageViewModel() {
_mangaList = new List<MangaItem>();
mangaList = new List<MangaItem>();
Initialize();
}
private async void Initialize() {
mangaList = await MangaListGet.GetListAsync();
}
public async void MainSearchSubmitted() {
mangaList = await MangaListGet.GetListAsync(_mainSearchText);
}
public void MangaSelected(object sender, ItemClickEventArgs e) {
var mangaItem = (MangaItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.MangaDetail), mangaItem.id);
}
}
And Detail Page ViewModel
class MangaDetailViewModel : ViewModelBase {
private MangaItem _mangaDetail;
public MangaItem mangaDetail {
get { return _mangaDetail; }
set { Set(ref _mangaDetail, value); }
}
private string _mangaId;
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode, IDictionary<string, object> suspensionState) {
_mangaId = parameter as string;
Initialize();
await Task.CompletedTask;
}
private async void Initialize() {
mangaDetail = await MangaDetailGet.GetAsync(_mangaId);
}
public void ChapterSelected(object sender, ItemClickEventArgs e) {
var _chapterId = (ChapterListItem)e.ClickedItem;
NavigationService.Navigate(typeof(Views.ChapterPage), _chapterId.id);
}
}
This code only shows the first problem is displaying previously loaded data for a split second. If needed I will add code that showcases the other problem, but I' not sure if it's really relevant right now. I'm thinking that maybe my entire logic is flawed or something.
EDIT:
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
where vm is xmlns:vm="using:MangaReader.ViewModels".
Another solution is to use Bootstrapper.ResolveforPage() which is intended to handle dependency injection but would easily serve your needs. Like this:
[Bindable]
sealed partial class App : BootStrapper
{
static ViewModels.DetailPageViewModel _reusedDetailPageViewModel;
public override INavigable ResolveForPage(Page page, NavigationService navigationService)
{
if (page.GetType() == typeof(Views.DetailPage))
{
if (_reusedDetailPageViewModel == null)
{
_reusedDetailPageViewModel = new ViewModels.DetailPageViewModel();
}
return _reusedDetailPageViewModel;
}
else
{
return null;
}
}
}
The NavigationService will treat this the same as any other view-model. Meaning it will call OnNavTo() and the other navigation overrides you include.
Best of luck.
While Template10 documentation states the NavigationCacheMode is disabled by default, that isn't the case in it's example templates (as of writing this). This is set in View C# code (.xaml.cs file).
.xaml.cs file
namespace MangaReader.Views {
public sealed partial class MangaDetail : Page {
public MangaDetail() {
InitializeComponent();
//NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled; //this was set by default
NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Disabled;
}
}
}
Now, new ViewModel will be created each time you access a this page.

Bind Pop-Up data into a screen from which Pop-up has triggered in WPF using MVVM

I am developing a WPF application using MVVM pattern in which one of the screen has a DataGrid and it has a button called Add Unit in a row on click of which it opens a pop-up as shown below:
(i created a new view and calling this view on click of this AddUnit button).So again this popup window has a datagrid with multiple rows as shown below:
My Question is how can I be able to bind the row data (only two columns ItemCode and ItemName)from Pop-up datagrid to the main window (without changing the data above the DataGrid in main Window) hope i am making sense or is there any other correct way of doing this.
I really have a Hard-time with this as I am new to WPF and MVVM., any help greatly appreciate.
Let's make these DataContexts(popup's and the grid from which the popup is opened) share the same Service(you can inject this service to these DataContexts in time they are created). This service will be updated each time the popup's grid selection of the new row is happens. In addition it(the service) will raise an event to inform about the fact that the selection in Popup grid happened and sends the selected data inside the EventArgs. The data context of the grid from which the popup is opened will listen to the shared service event and will update its grid ItemSource collection in the way you like.
Update
public class MainGridDataContext:BaseObservableObject
{
private readonly ILikeEventAggregator _sharedService;
//here we inject the the interface
public MainGridDataContext(ILikeEventAggregator sharedService)
{
_sharedService = sharedService;
//listen to selection changed
_sharedService.PopupGridSelectionHandler += SharedServiceOnPopupGridSelectionHandler;
}
//uncomment next c'tor if you don't have any injection mechanism,
//you should add the SharedService property to the App class
//public MainGridDataContext()
//{
// //_sharedService = App.Current.
// var app = Application.Current as App;
// if (app != null)
// {
// _sharedService = app.LikeEventAggregator;
// _sharedService.PopupGridSelectionHandler += SharedServiceOnPopupGridSelectionHandler;
// }
//}
private void SharedServiceOnPopupGridSelectionHandler(object sender, PopupGridData popupGridData)
{
UpdateGridWithAPopupSelectedData(popupGridData);
}
//method that helps to update the grid, you can do that in multiple ways
private void UpdateGridWithAPopupSelectedData(PopupGridData popupGridData)
{
//Update your DataGrid here.
}
}
public class PopupDataContext:BaseObservableObject
{
private readonly ILikeEventAggregator _sharedService;
//here we inject the the interface
public PopupDataContext(ILikeEventAggregator sharedService)
{
_sharedService = sharedService;
}
//uncomment next c'tor if you don't have any injection mechanism,
//you should add the SharedService property to the App class
//public PopupDataContext()
//{
// //_sharedService = App.Current.
// var app = Application.Current as App;
// if (app != null)
// {
// _sharedService = app.LikeEventAggregator;
// }
//}
//... your logic
private PopupGridData _selectedData;
//you should bind the popup grid selected value to this property
public PopupGridData SelectedData
{
get { return _selectedData; }
set
{
_selectedData = value;
OnPropertyChanged(() => SelectedData);
_sharedService.OnPopupGridSelectionHandler(_selectedData);
}
}
//... your logic
}
public class PopupGridData
{
public object PopupGridSelectedData { get; set; }
}
Shared service code
public interface ILikeEventAggregator
{
event EventHandler<PopupGridData> PopupGridSelectionHandler;
void OnPopupGridSelectionHandler(PopupGridData e);
}
public class LikeEventAggregator : ILikeEventAggregator
{
public event EventHandler<PopupGridData> PopupGridSelectionHandler;
public virtual void OnPopupGridSelectionHandler(PopupGridData e)
{
var handler = PopupGridSelectionHandler;
if (handler != null) handler(this, e);
}
}
Let me know if you need more info.
Regards.

Categories