Execute command rom another view Model

Eduardo Gomez Romero 1,355 Reputation points
2024-12-01T19:22:27.1033333+00:00

I know the mvvm pattern is equal to no code behind, but sometimes is impossible to avoid code behind, especially, when you need access to a UI component in your vm

My app has several pages

  1. ChargeStationPage
  2. TurbinesCollectionPage
  3. NewPage
  4. SuppertPage

I have a title bar in the chargeStatiopPage, and this title bar is accessible by any page

and this page executes a command when select a turbine

namespace METROWIND.Views
{
    public partial class ChargingStationsMapPage: ContentPage
    {
        public ChargingStationsMapPageViewModel PageViewModel { get; }

        public ChargingStationsMapPage(ChargingStationsMapPageViewModel stationsMapPageViewModel)
        {
            InitializeComponent();
            PageViewModel = stationsMapPageViewModel;
            BindingContext = PageViewModel;

            InitializeMap();
            InitializeTitleBar();
            DeviceHelper.AddOrRemoveContentBasedOnDevice(MobileContent);
        }

        private void InitializeMap()
        {
            PageViewModel.MapView = ChargingStationMap;
            PageViewModel.MapDialogPopUp = MapChangeTypuPopUp;
        }

        private void InitializeTitleBar()
        {
            var comboBox = CreateComboBox();
            comboBox.SelectionChanged += ComboBox_SelectionChanged;

            App.WindowInstance!.TitleBar = new TitleBar
            {
                Icon = "icon_win.png",
                Title = "METROWIND",
                BackgroundColor = Color.FromArgb("#FF3C155F"),
                HeightRequest = 48,
                Content = comboBox
            };
        }

        private SfComboBox CreateComboBox()
        {
            return new SfComboBox
            {
                Margin = new Thickness(5),
                IsEditable = true,
                ItemsSource = PageViewModel.TurbinePins,
                DisplayMemberPath = "Turbine.Name",
                TextMemberPath = "Turbine.Name",
                IsClearButtonVisible = false,
                HighlightedTextColor = Colors.Red,
                HighlightedTextFontAttributes = FontAttributes.Bold,
                IsFilteringEnabled = true
            };
        }

        private async void ComboBox_SelectionChanged(object? sender, SelectionChangedEventArgs e)
        {
            if (sender is SfComboBox comboBox && comboBox.SelectedValue is TurbinePin selectedPin)
            {
                // Zoom into the selected turbine's location
                PageViewModel.ItemSelectedCommand.Execute(selectedPin.Turbine);

                // Wait for a short duration before navigation
                await Task.Delay(1000);

                // Navigate to the turbine detail page
                await PageViewModel.PinMarkerClicked(selectedPin);
            }
        }
    }


This command comes from the AppShell, because this page inherits from the shell

namespace METROWIND.ViewModel
{
    public partial class ChargingStationsMapPageViewModel: AppShellViewModel
    {
        public SfPopup? MapDialogPopUp;

        public Map? MapView;

        public bool LoadedPins;

        [ObservableProperty]
        bool isExpanded;

        public ChargingStationsMapPageViewModel(ITurbineService turbineService, IAppService appService,
            IServiceProvider serviceProvider, ICommandHandler commandHandler)
            : base(turbineService, appService, serviceProvider, commandHandler)
        {
            MapDialogButtons();
        }

        public ObservableCollection<MapTypeButton> MapTypeButtons { get; set; } = [];


        private void MapDialogButtons()
        {
            MapTypeButtons.Add(new MapTypeButton
            {
                Caption = "Default",
                ImageName = FontAwesome.Road,
                Selected = true,
                MapNumber = 1
            });
            MapTypeButtons.Add(new MapTypeButton
            {
                Caption = "Satellite",
                MapNumber = 2,
                ImageName = FontAwesome.StreetView,
            });
        }

        [RelayCommand]
        void ItemSelected(Turbine Turbine)
        {
            if (Turbine == null || MapView == null)
            {
                return;
            }
            var mapSpan = MapSpan.FromCenterAndRadius(Turbine.Location,
                Distance.FromKilometers(2));

            MapView!.MoveToRegion(mapSpan);
        }


Now

I have a collctionPage, that gets all the turbine from the shell and display them in a collection view

using Map = Microsoft.Maui.ApplicationModel.Map;

namespace METROWIND.ViewModel
{
    public partial class TurbinesCollectionPageViewModel(ITurbineService turbineService,
        IAppService appService,
        IServiceProvider serviceProvider,
        ICommandHandler commandHandler): AppShellViewModel(turbineService, appService, serviceProvider, commandHandler)
    {
        public CollectionView? TurbinesCollection;
        public SfComboBox? ColletionComboBox;

        [ObservableProperty]
        Turbine? turbine;

        [RelayCommand]
        async Task SelectedItemChange()
        {
            if (ColletionComboBox!.SelectedIndex < 0)
            {
                return;
            }

            var item = TurbinePins.ElementAt(ColletionComboBox.SelectedIndex);
            TurbinesCollection?.ScrollTo(ColletionComboBox.SelectedIndex, -1, ScrollToPosition.Center);
            var inputView = ColletionComboBox.Children[1] as Entry;

#if ANDROID || IOS
            if (KeyboardExtensions.IsSoftKeyboardShowing(inputView!))
            {
                await Task.Delay(200);
                await inputView!.HideKeyboardAsync(default);
            }
#else
            await Task.CompletedTask;
#endif
        }

I want to tell the title bar to execute this method

  if (ColletionComboBox!.SelectedIndex < 0)
            {
                return;
            }

            var item = TurbinePins.ElementAt(ColletionComboBox.SelectedIndex);
            TurbinesCollection?.ScrollTo(ColletionComboBox.SelectedIndex, -1, ScrollToPosition.Center);
            var inputView = ColletionComboBox.Children[1] as Entry;

#if ANDROID || IOS
            if (KeyboardExtensions.IsSoftKeyboardShowing(inputView!))
            {
                await Task.Delay(200);
                await inputView!.HideKeyboardAsync(default);
            }
#else
            await Task.CompletedTask;
#endif
        }

when I am in the turbines colletion PageAlso, I don't know I can implement a little view that says

with the two turbines and says the name of the page and if I select one of them, it will go to that page

Developer technologies .NET .NET MAUI
{count} votes

Accepted answer
  1. Yonglun Liu (Shanghai Wicresoft Co,.Ltd.) 50,126 Reputation points Microsoft External Staff
    2024-12-04T03:07:01.7+00:00

    Hello,

    Thanks for your feedback.

    To achieve this requirement, the core requirement is how to get the comboBox from the TitleBar.

    Since Content is a read-only object in TitleBar, you cannot implement it by manipulating Content. To solve this problem, you need to inherit TitleBar through a custom class to expose an interface that can operate comboBox to the outside. I use SearchBar as a substitute for SfComboBox to implement this function, you can refer to the following code.

    Workaround:

    // Implement a custom TitleBar and implement methods for binding and unbinding events for SearchBar.
    public class MyTitleBar:TitleBar
    {
        public SearchBar searchBar { get; set; }
        public MyTitleBar()
        {
            // create your combox here
            searchBar = new SearchBar();
            SetEventForSearchBar();
            // create the titlebar
            Icon = "icon_win.png";
            Title = "METROWIND";
            BackgroundColor = Color.FromArgb("#FF3C155F");
            HeightRequest = 48;
            Content = searchBar;
        }
        public void SetEventForSearchBar()
        {
            searchBar.SearchButtonPressed += SearchBar_SearchButtonPressed;
        }
        public void RemoveEventForSearchBar()
        {
            searchBar.SearchButtonPressed -= SearchBar_SearchButtonPressed;
        }
        // Event for SearchBar
        private void SearchBar_SearchButtonPressed(object sender, EventArgs e)
        {
            Console.WriteLine("test");
        }
     
    }
    

    Since TitleBar has encapsulated the functions needed by the first page, when creating TitleBar, you only need to create an instance.

    App.WindowInstance!.TitleBar = new MyTitleBar();
    

    In other pages, you need to get the instance of the custom TitleBar to change the events of the internal controls.

    var tb = App.WindowInstance?.TitleBar as MyTitleBar;
    if (tb != null)
    {
        tb.RemoveEventForSearchBar();
        tb.searchBar.SearchButtonPressed += (s, e) =>
        {
            Console.WriteLine("This is another page.");
        };
    }
    

    Best Regards,

    Alec Liu.


    If the answer is the right solution, please click "Accept Answer" and kindly upvote it. If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.