RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 898835
Accepted
Павел Ериков
Павел Ериков
Asked:2020-10-28 06:00:15 +0000 UTC2020-10-28 06:00:15 +0000 UTC 2020-10-28 06:00:15 +0000 UTC

C# WPF MVVM 中的事件

  • 772

有 2 个 ViewModel 和 AuthViewModel 和 LoginViewModel。LoginView 有一个 Frame,它的内容是一个 LoginControl,它有一个 AuthViewModel 上下文。在这个 LoginControl 中有一个按钮,当单击时,LoginCompleted 事件应该会触发,并且该事件在 App.xaml.cs 中进行处理。

AuthViewModel.cs

 public event Action LoginCompleted;
    public ICommand AuthCommand => new RelayCommand(o => AuthMethod());


    public void AuthMethod()
    {
        LoginCompleted?.Invoke();
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

应用程序.xaml.cs

private LoginWindow Window { get; set; }
    private MainWindow CustomWindow { get; set; }
    public MainLoginVIewModel MainViewModel { get; set; }
    public LoginViewModel LoginViewModel { get; set; }

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        LoginViewModel = new LoginViewModel();
        MainViewModel = new MainLoginVIewModel(LoginViewModel);
        LoginViewModel.OnAuthorize += LoginViewModelOnOnAuthorize;
        Window = new LoginWindow { DataContext = MainViewModel };
        Window.Show();
    }

    private void LoginViewModelOnOnAuthorize(object sender, LoginEventArgs e)
    {
        if (e.IsAuthorized)
        {
            CustomWindow = new MainWindow { DataContext = new ContentViewModel(e.User) };
            CustomWindow.Show();
            Window.Close();
        }

    }

也就是说,通过单击 LoginControl 中的按钮,App.xaml.cs 中的事件应该会触发。如果我​​做错了什么,请告诉我如何更正确地实现它。

如果你需要更多,我会扔掉代码

注册控制.xaml

<Grid>
    <TextBox Text="{Binding User}" Width="100" Height="100"></TextBox>
</Grid>

RegisterViewModel.cs

class RegisterViewModel : VM
{
    private string user;
    public string User
    {
        get => user;
        set => Set(ref user, value);
    }
}

主授权窗口的ViewModel,我称之为MainLoginViewModel

RegisterViewModel regVM;
    public MainLoginVIewModel(LoginViewModel loginVM)
    {
        TestCommand = new RelayCommand(Test);
        regVM = new RegisterViewModel();
        CurrentContent = loginVM;
    }

    private VM currentContent;
    public VM CurrentContent
    {
        get => currentContent;
        set => Set(ref currentContent, value);
    }

    public ICommand TestCommand { get; }
    private void Test()
    {
        CurrentContent = regVM;
    }
c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    EvgeniyZ
    2020-10-28T09:15:29Z2020-10-28T09:15:29Z

    如果你这么快看你的描述,那么你这里至少有3个问题:

    1. 你在 App 中做了很多逻辑,这不是一个很好的解决方案。我认为把它放在那里没有意义。为此目的制作一个单独的VM,它将处理所有内容,为什么在APP中...
    2. 在 MVVM 中,您不能通过代码使用控件,例如,如果您有一个带有名称的 TextBox,x:Name="textBox1"并且您想以这种方式为其设置文本:textBox1.Text = "..."那么您直接使用此元素,根据 MVVM 规则,这是非常糟糕的!为此目的,有绑定(您指定<TextBox Text="{Binding MyTextProperty}">)。尝试从控件中删除所有名称,甚至完全删除 View 部分。根据 MVVM 模式,即使没有视图,您的应用程序也应该可以工作。
    3. Frame,这对于在窗口中显示内容来说不是一个很好的控件,它不适合 MVVM 模式。尝试使用此选项,它会变得更容易!

    让我们试着用我上面写的所有东西的实现来做一个简单的例子。让我们通过事件和内容更改来模拟授权:

    我将从一个空项目开始这一切,你自己看看......

    因此,首先,我们需要两个辅助类:

    • VM- 它实现INotifyPropertyChanged了,我们也将使用它来设置当前的内容。每个人都有不同的实现,我个人采取这个选项:

      public class VM : INotifyPropertyChanged
      {
          public event PropertyChangedEventHandler PropertyChanged;
      
          protected bool Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
          {
              if (EqualityComparer<T>.Default.Equals(field, value))
                  return false;
      
              field = value;
              NotifyPropertyChanged(propertyName);
              return true;
          }
      
          protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
          {
              PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
          }
      }
      
    • RelayCommand在 MVVM 中,不习惯使用控制事件(Click 等),它们被替换为绑定,命令 ( ICommand) 用于将逻辑附加到按钮。RelayCommand分别是这个接口的一个实现。再说一次,每个人都不一样,我个人会选择这个选项:

      public class RelayCommand<T> : ICommand
      {
          private Action<T> action;
          public RelayCommand(Action<T> action) => this.action = action;
          public bool CanExecute(object parameter) => true;
          #pragma warning disable CS0067
          public event EventHandler CanExecuteChanged;
          #pragma warning restore CS0067
          public void Execute(object parameter) => action((T)parameter);
      }
      
      public class RelayCommand : ICommand
      {
          private Action action;
          public RelayCommand(Action action) => this.action = action;
          public bool CanExecute(object parameter) => true;
          #pragma warning disable CS0067
          public event EventHandler CanExecuteChanged;
          #pragma warning restore CS0067
          public void Execute(object parameter) => action();
      }
      

    好的,准备工作已经完成。现在让我们为它们制作两个页面和一个虚拟机。

    1. 授权页面。在它上面,我们将询问用户的登录名和密码,并且在它的 VM 中,我们将实现一个在进行授权尝试时将被调用的命令。它本身LoginViewModel将从类继承VM(因此我们可以在更改内容时应用它,也可以使用 INPC)。

      • LoginViewModel:

        public class LoginViewModel : VM
        {
            public event EventHandler<LoginEventArgs> OnAuthorize;
        
            public ICommand LoginCommand { get; }
        
            public LoginViewModel()
            {
                LoginCommand = new RelayCommand(Authorize);
            }
        
            private string user;
            public string User
            {
                get => user;
                set => Set(ref user, value);
            }
        
            private string password;
            public string Password
            {
                get => password;
                set => Set(ref password, value);
            }
        
            private void Authorize()
            {
                if (User?.ToLower() == "test" && Password?.ToLower() == "123")
                {
                    OnAuthorize?.Invoke(this, new LoginEventArgs(User, true, "Успешная авторизация!"));
                }
                else
                {
                    OnAuthorize?.Invoke(this, new LoginEventArgs(User, false, "Неверный логин или пароль!"));
                }
            }
        }
        
      • LoginEventArgs是事件数据 ( EventArgs),一个简单的类,其中包含要在此事件中传递的数据的结构:

        public class LoginEventArgs : EventArgs
        {
            public string User { get; }
            public bool IsAuthorized { get; }
            public string Message { get; }
        
            public LoginEventArgs(string user, bool isAuthorized, string message)
            {
                User = user;
                IsAuthorized = isAuthorized;
                Message = message;
            }
        }
        
      • LoginView- 这是我们授权的视图,只有两个字段和一个按钮,我们将所有内容绑定到来自LoginViewModel. View 本身是一个用户控件 ( UserControl):

        <Grid VerticalAlignment="Center" HorizontalAlignment="Center">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
        
            <TextBlock Grid.Column="0" Grid.Row="0" Text="Логин: "/>
            <TextBlock Grid.Column="0" Grid.Row="1" Text="Пароль: "/>
            <TextBox Grid.Column="1" BorderThickness="0 0 0 1" Grid.Row="0" Text="{Binding User}" Width="100"/>
            <TextBox Grid.Column="1" BorderThickness="0 0 0 1"  Grid.Row="1" Text="{Binding Password}" Width="100"/>
            <Button Grid.Row="2" Margin="0 5" Background="Transparent" Grid.ColumnSpan="2" Grid.Column="0" Content="Войти" Command="{Binding LoginCommand}"/>
        </Grid>
        
    2. 包含我们将在授权后显示的内容的页面。我们完全通过类比来做所有事情LoginView:

      • ContentViewModel- 最简单的 ViewModel,例如,我将只包含一个属性。ViewModel 必须继承自 class VM,否则我们将无法将其设置为内容:

        public class ContentViewModel : VM
        {
            public string Title { get; set; } = "У нас получилось!";
        }
        
      • ContentView- 查看我们的主要内容。是UserControl,只包含绑定TextBlock:

        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="20" Text="{Binding Title}"/>
        

    现在让我们在应用程序的主窗口 ( MainWindow) 上工作。对他来说,我们还将创建自己的 ViewModel,我们将给MainViewModel它命名并将所有这些组合在其中:

    public class MainViewModel : VM
    {
        public LoginViewModel LoginViewModel { get; }
    
        public MainViewModel()
        {
            LoginViewModel = new LoginViewModel();
            LoginViewModel.OnAuthorize += LoginViewModelOnOnAuthorize;
            CurrentContent = LoginViewModel;
        }
    
        private VM currentContent;
        public VM CurrentContent
        {
            get => currentContent;
            set => Set(ref currentContent, value);
        }
    
        private string message;
        public string Message
        {
            get => message;
            set => Set(ref message, value);
        }
    
        private void LoginViewModelOnOnAuthorize(object sender, LoginEventArgs e)
        {
            Message = $"[{e.User}] {e.Message}";
            CurrentContent = e.IsAuthorized ? (VM) new ContentViewModel() : LoginViewModel;
        }
    }
    

    您可以看到我们创建了 Authorization ViewModel 属性,在构造函数中对其进行了初始化,订阅了事件,例如,将其设置为我们窗口的主要内容。例如,在事件处理程序中,我形成了一条分配给 created 属性的消息。

    现在本身MainWindow:

    <Grid>
        <Grid.Resources>
            <DataTemplate DataType="{x:Type vm:ContentViewModel}">
                <view:ContentView/>
            </DataTemplate>
            <DataTemplate DataType="{x:Type vm:LoginViewModel}">
                <view:LoginView/>
            </DataTemplate>
        </Grid.Resources>
        <StackPanel>
            <Border BorderThickness="0 0 0 1" BorderBrush="Gray" Padding="10" Margin="10">
                <TextBlock Text="{Binding Message}"/>
            </Border>
            <ContentPresenter Content="{Binding CurrentContent}"/>
        </StackPanel>
    </Grid>
    

    在帮助下DataTemplate,我们将每个 ViewModel 与它的 View 关联起来。我们还显示一条消息(通常的TextBlock,绑定到属性Message),以及页面的内容(我们将Frame其替换为 ),我们将其设置为ContentPresenter。

    我们还有待问DataContext,我们将通过重写来做到这一点App:

    • 我们App.xaml清理StartupUri="MainWindow.xaml"。
    • 在App.xaml.cs重新定义OnStartup:

      private MainWindow Window { get; set; }
      public MainViewModel MainViewModel { get; set; }
      
      protected override void OnStartup(StartupEventArgs e)
      {
          base.OnStartup(e);
          MainViewModel = new MainViewModel();
          Window = new MainWindow{DataContext = MainViewModel};
          Window.Show();
      }
      

    就是这样,让我们​​开始欣赏所做的工作:

    结果

    所以,很简单,我们摆脱了可怕的Frame,使用一个事件并在一个窗口中显示不同的内容。请注意,我们从未访问过任何 View 元素,我们可以完全删除它们,我们的逻辑将过上自己的生活。这种分层就是 MVVM !最重要的是了解它的工作原理,然后一切都会像发条一样!祝你好运!


    如果我们有不同的窗口,那么最简单的选择是执行以下操作:

    1. 让我们重写 MainVewModel,或者更确切地说是它的构造函数,让它接受 LoginViewModel:

      public MainViewModel(LoginViewModel loginVM)
      {
          LoginViewModel = loginVM;
          CurrentContent = LoginViewModel;
      }
      
    2. 让我们重写App。这里我们需要初始化LoginVM,MainVM,订阅事件,在事件中做开/关逻辑,设置窗口属性:

      public partial class App : Application
      {
          private MainWindow Window { get; set; }
          private CustomWindow CustomWindow { get; set; }
          public MainViewModel MainViewModel { get; set; }
          public LoginViewModel LoginViewModel { get; set; }
      
          protected override void OnStartup(StartupEventArgs e)
          {
              base.OnStartup(e);
              LoginViewModel = new LoginViewModel();
              MainViewModel = new MainViewModel(LoginViewModel);
              LoginViewModel.OnAuthorize += LoginViewModelOnOnAuthorize;
              Window = new MainWindow{DataContext = MainViewModel};
              Window.Show();
          }
      
          private void LoginViewModelOnOnAuthorize(object sender, LoginEventArgs e)
          {
              if (e.IsAuthorized)
              {
                  CustomWindow = new CustomWindow { DataContext = LoginViewModel };
                  CustomWindow.Show();
                  Window.Close();
                  return;
              }
      
              MainViewModel.Message = $"[{e.User}] {e.Message}";
          }
      }
      

    就是这样,现在在启动时,会打开带有授权内容的MainWindow,如果授权成功,则MainWindow将关闭,然后会出现CustomWindow(我们的另一个窗口)。

    窗口结果

    • 11

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    是否可以在 C++ 中继承类 <---> 结构?

    • 2 个回答
  • Marko Smith

    这种神经网络架构适合文本分类吗?

    • 1 个回答
  • Marko Smith

    为什么分配的工作方式不同?

    • 3 个回答
  • Marko Smith

    控制台中的光标坐标

    • 1 个回答
  • Marko Smith

    如何在 C++ 中删除类的实例?

    • 4 个回答
  • Marko Smith

    点是否属于线段的问题

    • 2 个回答
  • Marko Smith

    json结构错误

    • 1 个回答
  • Marko Smith

    ServiceWorker 中的“获取”事件

    • 1 个回答
  • Marko Smith

    c ++控制台应用程序exe文件[重复]

    • 1 个回答
  • Marko Smith

    按多列从sql表中选择

    • 1 个回答
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Suvitruf - Andrei Apanasik 什么是空? 2020-08-21 01:48:09 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5