Управление состояниями кнопки-переключателя из ViewModel MVVM

Я хотел бы контролировать состояния ToggleButton из модели представления, чтобы иметь возможность общаться с ней, может ли она быть переключена или нет.

Вот xaml для ToggleButton:

  <controls:ToggleButton Command = "{Binding StartStopCommand}" WidthRequest = "120" Margin = "10,10,10,10">
    <VisualStateManager.VisualStateGroups>
      <VisualStateGroup Name = "ToggleStates">
        <VisualState Name = "ToggledOff">
          <VisualState.Setters>
            <Setter Property = "Text" Value = "START" />
            <Setter Property = "FontSize" Value = "14" />
            <Setter Property = "BackgroundColor" Value = "#474747" />
            <Setter Property = "TextColor" Value = "White" />
          </VisualState.Setters>
        </VisualState>

        <VisualState Name = "ToggledOn">
          <VisualState.Setters>
            <Setter Property = "Text" Value = "STOP" />
            <Setter Property = "FontSize" Value = "14" />
            <Setter Property = "BackgroundColor" Value = "#6e1413" />
            <Setter Property = "TextColor" Value = "White" />
          </VisualState.Setters>
        </VisualState>
      </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
  </controls:ToggleButton>

С# часть ToggleButton:

  public class ToggleButton : Button
  {
    public event EventHandler<ToggledEventArgs> Toggled;

    public static BindableProperty IsToggledProperty =
        BindableProperty.Create(nameof(IsToggled), typeof(bool), typeof(ToggleButton), false, propertyChanged: OnIsToggledChanged);

    public ToggleButton()
    {
      Clicked += (sender, args) => IsToggled ^= true;
    }

    public bool IsToggled
    {
      set { SetValue(IsToggledProperty, value); }
      get { return (bool)GetValue(IsToggledProperty); }
    }

    protected override void OnParentSet()
    {
      base.OnParentSet();
      VisualStateManager.GoToState(this, "ToggledOff");
    }

    static async void OnIsToggledChanged(BindableObject bindable, object oldValue, object newValue)
    {
      ToggleButton toggleButton = (ToggleButton)bindable;
      bool isToggled = (bool)newValue;

      // Fire event
      toggleButton.Toggled?.Invoke(toggleButton, new ToggledEventArgs(isToggled));

      // Set the visual state
      VisualStateManager.GoToState(toggleButton, isToggled ? "ToggledOn" : "ToggledOff");
    }
  }

Здесь я хотел бы «отменить» переключение из ViewModel, если определенный логический результат неверен:

private async Task ActivateStartStopAsync()
{
  if (this.StartStopON == false)
  {
    // Do something
    this.StartStopON = true;
  }
  else
  {
    bool result = await App.Current.MainPage.DisplayAlert("About to be shut down", "Are you sure you want to turn it off?", "OK", "Cancel");

    if (result)
    {
      // Do something
      this.StartStopON = false;
    }
  }
}

public ICommand StartStopCommand { get; }

public MyViewModel()
{
   this.StartStopCommand = new Command(async () => await ActivateStartStopAsync());
}

В основном мне нужно изменить состояния кнопок из ViewModel. Как этого добиться? Это должно произойти как-то перед событием щелчка или я должен использовать для этого что-то вроде переключателя или флажка? Если да, то как я могу настроить флажок или переключатель, чтобы он выглядел как кнопка? Есть примеры?

Логика, которую я пытаюсь достичь:

  1. Кнопка серого цвета с текстом СТАРТ
  2. Кнопка нажата, цвет изменился на красный с текстом STOP
  3. Пользователь нажимает кнопку, появляется всплывающее окно с опциями «ОК» и «ОТМЕНА».
  4. Если пользователь нажимает OK -> кнопка снова становится серой с текстом START, всплывающее окно исчезает
  5. Если пользователь нажимает ОТМЕНА -> кнопка остается в том же состоянии = красный цвет с текстом STOP, всплывающее окно исчезает. Та же логика повторяется до тех пор, пока пользователь не нажмет OK во всплывающем окне.

Какую логическую операцию вы хотите выполнить, нажав на кнопку? Чтобы указать, можно ли щелкнуть кнопку, вы можете установить свойство IsEnabled в Button.

Wen xu Li - MSFT 21.03.2022 10:27

@WenxuLi-MSFT В основном я думаю, что моя функция работает так, как ожидалось, но мне не хватает возможности отменить ее состояния из ViewModel. Например, if (result) вернет false -> мне нужно оставить кнопку в ее предыдущем состоянии = отменить процесс запуска. Я думаю, что IsEnabled ограничивает любые операции с ним? Мне нужно просто иметь доступ к своим состояниям из ViewModel, а не полностью отключить или включить. Я могу обновить свой вопрос. В конце скоро напечатаю еще несколько пояснений.

10101 21.03.2022 11:45

поскольку вы упомянули MVVM, вам не нужно было бы привязывать его к свойству модели, которая реализует интерфейс INotifyThisOrTheOther (не могу вспомнить имя, прошло некоторое время с тех пор, как я играл с этим)

Noctis 24.03.2022 07:13

@HiFo, не могли бы вы отреагировать на данный ответ? Если вы прокомментируете это, сказав, что оно вас не удовлетворяет, другим будет предложено добавить дополнительные альтернативы. Если вы просто не отреагируете, я чувствую, что вы больше не заинтересованы в поиске решения.

deczaloth 24.03.2022 21:01
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
121
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вот что я придумал...

Окно.xaml:

Назначьте контекст данных для кнопки-переключателя и всплывающего окна, все обрабатывается в классе модели представления (Button_Toggle.cs).

Окно.xaml.cs:

Опять же, не так много.

Создайте экземпляр модели представления (Button_Toggle.cs) и назначьте контекст данных

Button_Toggle.cs:

Где происходят чудеса.

Здесь вы наследуете класс кнопки, поэтому у вас есть полный контроль и вы можете предоставить контекст данных кнопки.

Создайте общедоступный объект для всплывающего окна, это обеспечивает контекст данных для всплывающего окна.

Когда вводится какой-либо ввод, происходит обновление объекта buttonState, затем вносятся изменения, отражающие текущее состояние buttonState.

Я не думаю, что вам действительно нужно заходить так далеко, вы можете установить все на каждом из входов.

Однако для меня макет помогает в расширении и отладке в будущем.

Надеюсь, это поможет и имеет смысл, если нет, дайте мне знать.

Окно.xaml:

<Window x:Class = "Toggle_Change.MainWindow"
    xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local = "clr-namespace:Toggle_Change"
    mc:Ignorable = "d"
    Title = "MainWindow" Height = "350" Width = "525" Background = "Red">

<Window.DataContext>
    <local:Button_Toggle/>
</Window.DataContext>
<Grid>
    <local:Button_Toggle x:Name = "my_toggle_button" Background = "#FF727272"/>
   
    <Popup Name = "my_popup">
    </Popup>


</Grid>

Окно.xaml.cs:

using System.Windows;    

namespace Toggle_Change
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public Button_Toggle buttonToggle;


        public MainWindow()
        {
            InitializeComponent();

            buttonToggle = new Button_Toggle();

            this.my_toggle_button.DataContext = buttonToggle;
            this.my_popup.DataContext = buttonToggle.MyPopUp;

        }
    }
}

Button_Toggle.cs:

using System.Diagnostics;     
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

                                                            namespace Toggle_Change
{
    public class Button_Toggle   :System.Windows.Controls.Button
    {       

        public Popup MyPopUp;


        #region private class

        private enum StateType { NA,START,STOP,OK,CANCEL}       
        private class colors
        {
            public static readonly Brush Grey = new SolidColorBrush(color: System.Windows.Media.Color.FromArgb(0xFF,0x72,0x72,0x72));
            public static readonly Brush Black = new SolidColorBrush(color: System.Windows.Media.Color.FromArgb(0xFF,0x00,0x00,0x00));
            public static readonly Brush Red = new SolidColorBrush(color: System.Windows.Media.Color.FromArgb(0xFF,0xFF,0x00,0x00));
        }

        #endregion

        #region private objects
        private StateType buttonState = StateType.START;

        #endregion
                     
        /// <summary>
        /// update text based on the button state
        /// </summary>
        private void set_text()
        {
            switch (buttonState)
            {
                case StateType.START:

                    this.Content = "START";
                    this.Background = colors.Grey;
                    this.Foreground = colors.Black;

                    break;
                case StateType.STOP:
                    this.Content = "STOP";
                    this.Background = colors.Grey;
                    this.Foreground = colors.Red;  

                    break;
                case StateType.OK:
                    break;
                case StateType.CANCEL:
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// something was clicked, alter the button state based on the current state
        /// </summary>
        private void on_click()
        {
            Debug.WriteLine("CLICK");
            switch (buttonState)
            {
                case StateType.NA:

                    buttonState = 
                        StateType.START;

                    break;
                case StateType.START: 

                    buttonState = 
                        StateType.STOP;

                    break;
                case StateType.STOP:

                    MyPopUp.IsEnabled = true;
                    MyPopUp.IsOpen = true;
                    MyPopUp.Visibility = System.Windows.Visibility.Visible;   

                    break;
                case StateType.OK:

                    buttonState = 
                        StateType.START;

                    MyPopUp.IsEnabled = false;
                    MyPopUp.IsOpen = false;
                    MyPopUp.Visibility = System.Windows.Visibility.Hidden;

                    break;
                case StateType.CANCEL:

                    buttonState =
                        StateType.STOP;

                    MyPopUp.IsEnabled = false;
                    MyPopUp.IsOpen = false;
                    MyPopUp.Visibility = System.Windows.Visibility.Hidden;
                    break;
                default:
                    break;
            }
                                                             
            set_text();
        }
               
        private void ini_popup()
        {                          
            StackPanel _stackPanel;
            Button _btnA;
            Button _btnB;

            _btnA = new Button();
            _btnA.Content = "OK";
            _btnA.Click += _btnA_Click;

            _btnB = new Button();
            _btnB.Content = "NO";
            _btnB.Click += _btnB_Click;
                                           

            _stackPanel =
                new StackPanel();

            _stackPanel.Orientation = Orientation.Horizontal;

            _stackPanel.Children.Add(
                _btnA);

            _stackPanel.Children.Add(
                _btnB);   

            MyPopUp = new Popup();

            MyPopUp.Child = _stackPanel;

            MyPopUp.Placement = PlacementMode.Center;
            MyPopUp.PlacementTarget = this;

            MyPopUp.PopupAnimation = PopupAnimation.Slide;
            MyPopUp.Visibility = System.Windows.Visibility.Hidden;   
        }

        private void _btnB_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            buttonState = StateType.CANCEL;                         
            OnClick();    
        } 
        private void _btnA_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            buttonState = StateType.OK;                            
            OnClick(); 
        }

        private void MyPopUp_IsVisibleChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
        {
            Debug.WriteLine("Popup Is Doing Something");
        }

        private void Button_Toggle_Click(object sender, System.Windows.RoutedEventArgs e)
        {
            on_click();
        }

        public Button_Toggle() {

            ini_popup();

            MyPopUp.IsVisibleChanged += MyPopUp_IsVisibleChanged; 

            this.Click += Button_Toggle_Click;
            this.set_text();   

        }
    }
}

Другие вопросы по теме