I have a .Net Maui app and I'm not using Shell.
Using shell I would do the following to navigate between pages.
App.ApplicationShell.PushAsync(new SomePage());
Now without using the shell, does anyone know how to navigate between pages please ?
Here is my App class :
public partial class App : Application
{
//public static AppShell ApplicationShell; <<< No shell
public App()
{
InitializeComponent();
//App.ApplicationShell = new AppShell(); << No shell
//MainPage = ApplicationShell;
this.MainPage = new SomePage();
}
}
Thanks.
Cheers,
You can use the command to control the navigation.
First, Writing the command code in the xaml file.
<TextCell Text="this is a text"
Detail="Select text on focus"
Command="{Binding NavigateCommand}"
CommandParameter="{x:Type views:NextPage}" />
You can put the Command and CommandParameter in the element. CommandParameter contains the page name which you want to go.
Second, you can create a ICommand named NavigateCommand to bind the Command in xaml and write a method to control the Command.
public ICommand NavigateCommand { get; private set; }
public MainPage()
{
InitializeComponent();
NavigateCommand = new Command<Type>(
async (Type pageType) =>
{
Page page = (Page)Activator.CreateInstance(pageType);
await Navigation.PushAsync(page);
});
BindingContext = this;
}
NavigateCommand will pass the data to this method and change the type to Page then you can use the await Navigation.PushAsync(page); method to navigate the page.
Related
I have a simple model:
public sealed partial class ResultsModel : ObservableObject {
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
[NotifyCanExecuteChangedFor(nameof(ClearCommand))]
[ObservableProperty]
ObservableCollection<Arrivals> _arrivals = new();
public RelayCommand SaveCommand { get; private set; }
public RelayCommand ClearCommand { get; private set; }
internal ResultsModel() {
SaveCommand = new RelayCommand(SaveRequest, CanSaveClear);
ClearCommand = new RelayCommand(OnClear, CanSaveClear);
}
public bool CanSaveClear() {
return _arrivals.Count > 0;
}
void OnClear() {
_arrivals.Clear();
}
async void SaveRequest() {
// save stuff
}
}
// c#
DataContext = (model = new ResultsModel());
...
model.Arrivals.insert(0, thing);
// The _arrivals are bound to an ItemsRepeater and appear in gui as //they're added
<ItemsRepeater ItemsSource="{Binding Arrivals}">
<Button Content="Clear" Command="{Binding ClearCommand}"/>
<Button Content="Save" Command="{Binding SaveCommand}" />
I've bound buttons to the two Commands and they work ok, I just can't work how to get the canExecute code to run more than one time.
I was expecting that when items get added to the _arrivals collection (and they do) the canExecutes would be re-evaluated via the NotifyCanExecuteChangedFor attribute, but I'm obviously missing some glue somewhere because the button are always disabled.
Any help would be appreciated.
It won't happen when you added an item to the Arrivals. But it will happen when you change the Arrivals by giving a new ObservableCollection. You could create a simple string property to test this behavior.
The reason for this behavior is that when the NotifyCanExecuteChangedFor Attribute is used, the IRelayCommand.NotifyCanExecuteChanged will be called when the setter of the property is called. In your scenario, that means only when the setter of the Arrivals property is called, this Attribute will call the IRelayCommand.NotifyCanExecuteChanged.
I have 2 Views. One has an event, that passes two variables to the second page and loads the page up:
private void CollectionView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var item = (GalleryListEntry)e.CurrentSelection.FirstOrDefault();
Navigation.PushModalAsync(new Gallery(item.PhotographerCode, item.GalleryCode));
}
in the second page I have this:
public Gallery(string photographerCode, string galleryCode)
{
InitializeComponent();
}
The second page, has a Collection view that has its own Bindingsource.
For this bindingsource i have a Model, a Service, and a ViewModel. The service is called by the Viewmodel, and returns a List of images to be shown in the second page's collection view.
Inside this service class, I would need to access to the two variables passed above (photograperCodeand galleryCode) but I cannot figure out how to pass the variables to the ViewModel so i can then forward it to the class.
ViewModel:
using GalShare.Model;
using GalShare.Service;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
namespace GalShare.ViewModel
{
class GalleryViewModel
{
public ObservableCollection<Gallery> Galleries { get; set; }
public GalleryViewModel()
{
Galleries = new GalleryService().GetImageList();
}
}
}
I tried like this
((GalleryViewModel)this.BindingContext).pCode = photographerCode;
((GalleryViewModel)this.BindingContext).gCode = galleryCode;
but I get this error: System.NullReferenceException: 'Object reference not set to an instance of an object.'
BindingContext is Null, but in the Xaml file i have this:
<ContentPage.BindingContext>
<vm:GalleryViewModel/>
</ContentPage.BindingContext>
This should work fine. First in your Gallery
public Gallery(string photographerCode, string galleryCode)
{
InitializeComponent();
BindingContext = new GalleryViewModel(photographerCode, galleryCode);
}
And now in the ViewModel
class GalleryViewModel
{
public ObservableCollection<Gallery> Galleries { get; set; }
public GalleryViewModel(string pCode, string gCode)
{
this.pCode = pCode;
this.gCode = gCode;
Galleries = new GalleryService().GetImageList();
}
}
In this thread : Can anybody provide any simple working example of the Conductor<T>.Collection.AllActive usage? I've had part of an answer but I'm still a but confused.
I would simply like to reference all my view models into my ShellViewModel to be able to open/close ContentControls, but without injecting all of them in the constructor.
In the answer, it is suggested to inject an interface in the constructor of the ShellViewModel. If I do that, do I have to inject all my ViewModels in a class that implements that interface?
public MyViewModel(IMagicViewModelFactory factory)
{
FirstSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
SecondSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
ThirdSubViewModel = factory.MagicallyGiveMeTheViewModelIWant();
Items.Add(FirstSubViewModel);
Items.Add(SecondSubViewModel);
Items.Add(ThirdSubViewModel);
}
Also, I would like to avoid going through IoC.Get<> to get the instances of my view Models, I think it violates the principles of IoC if I am not mistaken.
In a few other examples, they create new viewModels when needed, but what's the point of using IoC in that case, especially when I need services injected inside those new ViewModels?
In my Shell view, I have a layout with 3 different areas, bound to my shell view model by :
<ContentControl x:Name="Header"
Grid.ColumnSpan="3"/>
<ContentControl x:Name="Menu"
Grid.Row="1"/>
<ContentControl x:Name="Main"
Grid.ColumnSpan="3"/>
In my ShellViewModel extending Conductor.Collection.AllActive, I reference the 3 areas like this:
public Screen Menu { get; private set; }
public Screen Header { get; private set; }
public Screen Main { get; private set; }
I would like to be able to change them like so:
Menu = Items.FirstOrDefault(x => x.DisplayName == "Menu");
Header = Items.FirstOrDefault(x => x.DisplayName == "Header");
Main = Items.FirstOrDefault(x => x.DisplayName == "Landing");
All my ViewModels have a DisplayName set in their constructor.
I have tried this but GetChildren() is empty
foreach (var screen in GetChildren())
{
Items.Add(screen);
}
Am I missing something obvious?
Thanks in Advance!
Finally, I managed to find an answer myself. It's all in the AppBootstrapper!
I ended up creating a ViewModelBase for my Screens so that they could all have an IShell property (so that the ViewModels could trigger a navigation in the ShellViewModel) like so:
public class ViewModelBase : Screen
{
private IShell _shell;
public IShell Shell
{
get { return _shell; }
set
{
_shell = value;
NotifyOfPropertyChange(() => Shell);
}
}
}
then in the AppBoostrapper registered them like this :
container.Singleton<ViewModelBase, SomeViewModel>();
container.Singleton<ViewModelBase, AnotherViewModel>();
container.Singleton<ViewModelBase, YetAnotherViewModel>();
then created an IEnumerable to pass as param to my ShellViewModel ctor:
IEnumerable<ViewModelBase> listScreens = GetAllScreenInstances();
container.Instance<IShell>(new ShellViewModel(listScreens));
then passing the IShell to each ViewModels
foreach (ViewModelBase screen in listScreens)
{
screen.Shell = GetShellViewModelInstance();
}
for the sake of completeness, here are my GetAllScreenInstances() and GetShellViewModelInstance() :
protected IEnumerable<ViewModelBase> GetAllScreenInstances()
{
return container.GetAllInstances<ViewModelBase>();
}
protected IShell GetShellViewModelInstance()
{
var instance = container.GetInstance<IShell>();
if (instance != null)
return instance;
throw new InvalidOperationException("Could not locate any instances.");
}
Here's what my ShellViewModel ctor looks like:
public ShellViewModel(IEnumerable<ViewModelBase> screens )
{
Items.AddRange(screens);
}
Hope this can help someone in the future!
Here is the setup:
I have an autocompletebox that is being populated by the viewmodel which gets data from a WCF service. So it's quite straightforward and simple so far.
Now, I am trying to follow the principles of MVVM by which the viewmodel doesn't know anything about the view itself. Which is good, because I bound the Populating event of the autocomplete box to a method of my viewmodel via triggers and commands.
So the view model is working on fetching the data, while the view is waiting. No problems yet.
Now, the view model got the data, and I passed the collection of results to a property bound to the ItemSource property of the control. Nothing happens on the screen.
I go to MSDN and to find the officially approved way on how this situation is supposed to be handled (http://msdn.microsoft.com/en-us/library/system.windows.controls.autocompletebox.populating(v=vs.95).aspx):
Set the MinimumPrefixLength and MinimumPopulateDelay properties to
values larger than the default to minimize calls to the Web service.
Handle the Populating event and set the PopulatingEventArgs.Cancel
property to true.
Do the necessary processing and set the ItemsSource property to the
desired item collection.
Call the PopulateComplete method to signal the AutoCompleteBox to show
the drop-down.
Now I see a big problem with the last step because I don't know how I can call a method on a view from the view model, provided they don't know (and are not supposed to know!) anything about each other.
So how on earth am I supposed to get that PopulateComplete method of view called from the view model without breaking MVVM principles?
If you use Blend's Interactivity library, one option is an attached Behavior<T> for the AutoCompleteBox:
public class AsyncAutoCompleteBehavior : Behavior<AutoCompleteBox>
{
public static readonly DependencyProperty SearchCommandProperty
= DependencyProperty.Register("SearchCommand", typeof(ICommand),
typeof(AsyncAutoCompleteBehavior), new PropertyMetadata(null));
public ICommand SearchCommand
{
get { return (ICommand)this.GetValue(SearchCommandProperty); }
set { this.SetValue(SearchCommandProperty, value); }
}
protected override void OnAttached()
{
this.AssociatedObject.Populating += this.PopulatingHook;
}
protected override void OnDetaching()
{
this.AssociatedObject.Populating -= this.PopulatingHook;
}
private void PopulatingHook(object sender, PopulatingEventArgs e)
{
var command = this.SearchCommand;
var parameter = new SearchCommandParameter(
() => this.AssociatedObject
.Dispatcher
.BeginInvoke(this.AssociatedObject.PopulateComplete),
e.Parameter);
if (command != null && command.CanExecute(parameter))
{
// Cancel the pop-up, execute our command which calls
// parameter.Complete when it finishes
e.Cancel = true;
this.SearchCommand.Execute(parameter);
}
}
}
Using the following parameter class:
public class SearchCommandParameter
{
public Action Complete
{
get;
private set;
}
public string SearchText
{
get;
private set;
}
public SearchCommandParameter(Action complete, string text)
{
this.Complete = complete;
this.SearchText = text;
}
}
At this point you need to do 2 things:
Wire up the Behavior
<sdk:AutoCompleteBox MinimumPopulateDelay="250" MinimumPrefixLength="2" FilterMode="None">
<i:Interaction.Behaviors>
<b:AsyncAutoCompleteBehavior SearchCommand="{Binding Search}" />
</i:Interaction.Behaviors>
</sdk:AutoCompleteBox>
Create a DelegateCommand which handles your aysnc searching.
public class MyViewModel : ViewModelBase
{
public ICommand Search
{
get;
private set;
}
private void InitializeCommands()
{
this.Search = new DelegateCommand<SearchCommandParamater>(DoSearch);
}
private void DoSearch(SearchCommandParameter parameter)
{
var client = new WebClient();
var uri = new Uri(
#"http://www.example.com/?q="
+ HttpUtility.UrlEncode(parameter.SearchText));
client.DownloadStringCompleted += Downloaded;
client.DownloadStringAsync(uri, parameter);
}
private void Downloaded(object sender, DownloadStringCompletedEventArgs e)
{
// Do Something with 'e.Result'
((SearchCommandParameter)e.UserState).Complete();
}
}
How would I go about resolving a ViewModel's commands from a controller?
Right now I'm having to dependency inject the UnityContainer into the ViewModel via constructor, and resolve the ICommand with a string. I don't really want to have to pass the container to my viewmodel, and would prefer to keep it in my controller.
These are just snippets, not the whole thing. Not that they're that complex of classes yet though while I try to learn.
ViewModel
private ICommand loadedCommand;
public ICommand LoadedCommand
{
get { return loadedCommand; }
set
{
loadedCommand = value;
RaisePropertyChanged(() => this.LoadedCommand);
}
}
public MainViewModel(IUnityContainer container)
{
LoadedCommand = container.Resolve<ICommand>("LoadedCommand")
}
Controller
DelegateCommand LoadedCommand;
new DelegateCommand(() => ViewLoaded());
Controller
Container.RegisterInstance<ICommand>("LoadedCommand", LoadedCommand);
I don't even know if I'm going about this the right way. I'm sort of diving into everything at once, with Prism and Dependency Injection and mvvm(with controllers) being relatively new to me.
I may not be entirely sure about your question. But the way I understand commanding in MVVM is: You have the view and instead of click events you use command objects.
The reason is, that click events go to the codebehind, and using command instead you have the ability to transfer the logic to the viewmodel.
The view uses binding to connect to the viewmodel.
So a short example:
(Usage):
<Element Property="{Binding PropertyNameInTheViewModel}" />
(Example):
<Button Command="{Binding ClickCommand}" />
Then of course make the View's datacontext to the viewmodel. Example (done in the codebehind):
public partial class View : Window
{
public View(ViewModel vm)
{
InitializeComponent();
this.DataContext = vm:
}
}
In the viewmodel
public ICommand ClickCommand { get; set; }
#region constructor
public Viewmodel()
{
ClickCommand = new DelegateCommand(ButtonClick);
}
#endregion
private void ButtonClick()
{
// handle the click
}
I'm not entirely sure what you mean by 'controller'. The design pattern as I've learned, goes View -> ViewModel -> Model. And use dependency injection in to the view constructor in order to pass the viewmodel. Personally I'm learning MEF (Managed Extensibility Framework) instead of Unity.