4

我正在将我的第一个 MVVM 项目放在一起。我有一个状态栏,它将从应用程序内的各种视图(用户控件)更新。每个视图都有自己的 DataContext。我最初的想法是创建一个 ViewModelBase 类,它实现了 INotifyPropertyChanged 接口,还包含一个公共属性来绑定我的 StatusBar 的文本。应用程序中的所有其他 ViewModel 都将继承 ViewModelBase 类。当然,这是行不通的。我怎样才能做到这一点?我没有使用 MVVM Light 或任何其他框架,而是在 vb.net 中编程。提前致谢。

更新 - 以下是 Garry 在第二个答案中提出的翻译,我仍然无法从 MainViewModel 修改状态文本?有人看到他的 c# 代码的 vb 翻译有问题吗?这种 MVVM 过渡导致大量脱发!

在此处输入图像描述

ViewModelBase.vb

Imports System.ComponentModel

Public Class ViewModelBase
    Implements INotifyPropertyChanged

    Public Event PropertyChanged As PropertyChangedEventHandler _
        Implements INotifyPropertyChanged.PropertyChanged

    Protected Sub OnPropertyChanged(ByVal propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

End Class

状态视图模型.vb

Public Interface IStatusBarViewModel
    Property StatusBarText() As String
End Interface

Public Class StatusBarViewModel
    Inherits ViewModelBase
    Implements IStatusBarViewModel

    Private _statusBarText As String
    Public Property StatusBarText As String Implements IStatusBarViewModel.StatusBarText
        Get
            Return _statusBarText
        End Get
        Set(value As String)
            If value <> _statusBarText Then
                _statusBarText = value
                OnPropertyChanged("StatusBarText")
            End If
        End Set
    End Property
End Class

主视图模型.vb

Public Class MainViewModel
    Inherits ViewModelBase

    Private ReadOnly _statusBarViewModel As IStatusBarViewModel
    Public Sub New(statusBarViewModel As IStatusBarViewModel)
        _statusBarViewModel = statusBarViewModel
        _statusBarViewModel.StatusBarText = "Test"
    End Sub

End Class

Status.xaml (用户控件)

<StatusBar DataContext="{Binding StatusViewModel}">
...
<w:StdTextBlock Text="{Binding StatusText, UpdateSourceTrigger=PropertyChanged}" />

应用程序.xaml.vb

Class Application

    Protected Overrides Sub OnStartup(e As System.Windows.StartupEventArgs)
        Dim iStatusBarViewModel As IStatusBarViewModel = New StatusBarViewModel()
        Dim mainViewModel As New MainViewModel(iStatusBarViewModel)
        Dim mainWindow As New MainWindow() With { _
            .DataContext = mainViewModel _
        }
        mainWindow.Show()
    End Sub

End Class

主窗口.xaml

<Window x:Class="MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:GlobalStatusBarTest"
    Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="20"/>
        </Grid.RowDefinitions>
        <local:Status Grid.Row="1" />
    </Grid>
</Window>
4

4 回答 4

1

首先,使用您的主窗口、应用程序外壳,并将其更改为类似这样的内容...

    <DockPanel>
        <sample:StatusBarControl DockPanel.Dock="Bottom" x:Name="StatusBarRegion"/> 
        <sample:MainContentControl DockPanel.Dock="Top" x:Name="MainContentRegion"/>
    </DockPanel>
</Grid>

这将外壳划分为非常类似于 Prism 区域管理器的区域。请注意,“内容”现在是一个用户控件,以及您想要放置在该区域中的任何其他视图。另请注意,状态栏有一个区域,但不会更改其视图或视图模型。

在你的shell后面的代码中,放置两个这样的依赖属性......

 public MainWindow()
        {
            InitializeComponent();
        }
        #region MainContentDp (DependencyProperty)
        public object MainContentDp
        {
            get { return GetValue(MainContentDpProperty); }
            set { SetValue(MainContentDpProperty, value); }
        }
        public static readonly DependencyProperty MainContentDpProperty =
            DependencyProperty.Register("MainContentDp", typeof(object), typeof(MainWindow),
              new PropertyMetadata(OnMainContentDpChanged));
        private static void OnMainContentDpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MainWindow mainWindow = d as MainWindow;
            if (mainWindow != null)
            {
                mainWindow.MainContentRegion.DataContext = e.NewValue;
            }
        }
        #endregion
        #region StatusBarDp (DependencyProperty)
        public object StatusBarDp
        {
            get { return GetValue(StatusBarDpProperty); }
            set { SetValue(StatusBarDpProperty, value); }
        }
        public static readonly DependencyProperty StatusBarDpProperty =
            DependencyProperty.Register("StatusBarDp", typeof(object), typeof(MainWindow),
              new PropertyMetadata(OnStatusBarDpChanged));
        private static void OnStatusBarDpChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MainWindow mainWindow = d as MainWindow;
            if (mainWindow != null)
            {
                mainWindow.StatusBarRegion.DataContext = e.NewValue;
            }
        }
        #endregion

这些依赖属性是 Prism 区域管理器的替代品。每个依赖属性设置其关联区域的数据上下文。在您的情况下,您希望更改 MainContentDp 以在视图模型之间来回切换。

在 app.xaml.cs 文件中,您可以覆盖启动方法,使其看起来像这样......

public partial class App 
{
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
        IStatusBarViewModel iStatusBarViewModel = new StatusBarViewModel();
        MainViewModel mainViewModel = new MainViewModel(iStatusBarViewModel);
        OtherViewModel otherViewModel = new OtherViewModel(iStatusBarViewModel);
        MainWindow mainWindow = new MainWindow
            {
                StatusBarDp = iStatusBarViewModel, 
                MainContentDp = mainViewModel
            };
        mainWindow.Show();
    }
}

此代码创建外壳并分配两个依赖属性,这反过来将使用它们各自的视图模型填充区域。

视图模型看起来像这样......

public class MainViewModel : ViewModelBase
{
    public ICommand ClickCommand { get; set; }
    private readonly IStatusBarViewModel _statusBarViewModel;
    public MainViewModel(IStatusBarViewModel statusBarViewModel)
    {
        _statusBarViewModel = statusBarViewModel;
        ClickCommand = new RelayCommand(ExecuteClickCommand, CanExecuteClickCommand);
    }
    private void ExecuteClickCommand(object obj)
    {
        _statusBarViewModel.StatusBarText = "Updating the db";
    }
    private bool CanExecuteClickCommand(object obj)
    {
        return true;
    }
    public void DoSomethingVeryImportant()
    {
        _statusBarViewModel.StatusBarText = "Starting some work";
        // do some work here
        _statusBarViewModel.StatusBarText = "Done doing some work";
    }
}

还有另一个视图模型...

public class OtherViewModel : ViewModelBase
{
    private readonly IStatusBarViewModel _statusBarViewModel;
    public OtherViewModel(IStatusBarViewModel statusBarViewModel)
    {
        _statusBarViewModel = statusBarViewModel;
    }
    public void UpdateTheDatabase()
    {
        _statusBarViewModel.StatusBarText = "Starting db update";
        // do some work here
        _statusBarViewModel.StatusBarText = "Db update complete";
    }        
}

}

两个 VM 在其构造函数中都获得相同的状态栏,并且都写入相同的状态栏。两个 VM 轮流在你的 shell 中共享同一个区域,“MainContentRegion”。Prism 无需冗长的答案即可完成所有这些操作,但由于您在 VB 中并且不使用 Prism,因此这种方法可以正常工作。

于 2013-07-29T19:07:51.133 回答
0

有几种选择可以以一种非常干净的方式实现这一目标。

当使用 Prism 或 MvvmLight 风格的框架时,可以使用某种“事件聚合器”或“信使”类在应用程序的不同部分之间发送消息。考虑一个状态栏视图,包含一个文本块和一个进度条。将有一个视图模型:

Class StatusViewModel
    Public Property Progress() As Integer
    ...
    Public Property Text() As String
    ...
End Class

无需创建从一切到 StatusViewModel 的依赖关系,我们可以使用共享服务发送包含更改进度或文本的信息的消息。例如

'progress changed to 75%
Messenger.SendMessage(Of ProgressChangedMessage)(New ProgressChangedMessage(75))

然后StatusViewModel可以根据入站消息更新其进度:

Messenger.[AddHandler](Of ProgressChangedMessage)(Function(message) 
    Me.Progess = message.NewProgress
End Function)

第二种解决方案是与每个想要更改进度或状态栏文本的视图模型共享 StatusViewModel。依赖注入服务定位使这很容易,但如果您想保持简单,您可以使用单例实例来访问StatusViewModel.

当您将 StatusViewModel 公开给其他类时,使用接口可能是明智之举,例如:

Public Interface IStatusService
    Sub SetProgress(progress As Integer)
    Function SetText(text As String) As String
End Interface

编辑:将代码更改为 vb。

于 2013-07-18T19:15:29.773 回答
0

您可以使用消息。概括:

  1. 您的 StatusBar VM 侦听“UpdateStatusBar”消息
  2. 您发送一条消息“UpdateStatusBar”,其中包含您要编写的消息的字符串。这可以从代码的任何部分完成(甚至在 VM 之外)

信使:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

//Messenger downloaded from StackOverflow and small modifications made
namespace GuitarDiary.Desktop.Utility
{
    public class Messenger
    {
        private static readonly object CreationLock = new object();
        private static readonly ConcurrentDictionary<MessengerKey, object> Dictionary = new ConcurrentDictionary<MessengerKey, object>();

        #region Default property

        private static Messenger _instance;

        /// <summary>
        /// Gets the single instance of the Messenger.
        /// </summary>
        public static Messenger Default
        {
            get
            {
                if (_instance == null)
                {
                    lock (CreationLock)
                    {
                        if (_instance == null)
                        {
                            _instance = new Messenger();
                        }
                    }
                }

                return _instance;
            }
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the Messenger class.
        /// </summary>
        private Messenger()
        {
        }

        /// <summary>
        /// Registers a recipient for a type of message T. The action parameter will be executed
        /// when a corresponding message is sent.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="recipient"></param>
        /// <param name="action"></param>
        public void Register<T>(object recipient, Action<T> action)
        {
            Register(recipient, action, null);
        }

        /// <summary>
        /// Registers a recipient for a type of message T and a matching context. The action parameter will be executed
        /// when a corresponding message is sent.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="recipient"></param>
        /// <param name="action"></param>
        /// <param name="context"></param>
        public void Register<T>(object recipient, Action<T> action, object context)
        {
            var key = new MessengerKey(recipient, context);
            Dictionary.TryAdd(key, action);
        }

        /// <summary>
        /// Unregisters a messenger recipient completely. After this method is executed, the recipient will
        /// no longer receive any messages.
        /// </summary>
        /// <param name="recipient"></param>
        public void Unregister(object recipient)
        {
            Unregister(recipient, null);
        }

        /// <summary>
        /// Unregisters a messenger recipient with a matching context completely. After this method is executed, the recipient will
        /// no longer receive any messages.
        /// </summary>
        /// <param name="recipient"></param>
        /// <param name="context"></param>
        public void Unregister(object recipient, object context)
        {
            object action;
            var key = new MessengerKey(recipient, context);
            Dictionary.TryRemove(key, out action);
        }

        /// <summary>
        /// Sends a message to registered recipients. The message will reach all recipients that are
        /// registered for this message type.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="message"></param>
        public void Send<T>(T message)
        {
            Send(message, null);
        }

        /// <summary>
        /// Sends a message to registered recipients. The message will reach all recipients that are
        /// registered for this message type and matching context.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="message"></param>
        /// <param name="context"></param>
        public void Send<T>(T message, object context)
        {
            IEnumerable<KeyValuePair<MessengerKey, object>> result;

            if (context == null)
            {
                // Get all recipients where the context is null.
                result = from r in Dictionary where r.Key.Context == null select r;
            }
            else
            {
                // Get all recipients where the context is matching.
                result = from r in Dictionary where r.Key.Context != null && r.Key.Context.Equals(context) select r;
            }

            foreach (var action in result.Select(x => x.Value).OfType<Action<T>>())
            {
                // Send the message to all recipients.
                action(message);
            }
        }

        protected class MessengerKey
        {
            public object Recipient { get; private set; }
            public object Context { get; private set; }

            /// <summary>
            /// Initializes a new instance of the MessengerKey class.
            /// </summary>
            /// <param name="recipient"></param>
            /// <param name="context"></param>
            public MessengerKey(object recipient, object context)
            {
                Recipient = recipient;
                Context = context;
            }

            /// <summary>
            /// Determines whether the specified MessengerKey is equal to the current MessengerKey.
            /// </summary>
            /// <param name="other"></param>
            /// <returns></returns>
            protected bool Equals(MessengerKey other)
            {
                return Equals(Recipient, other.Recipient) && Equals(Context, other.Context);
            }

            /// <summary>
            /// Determines whether the specified MessengerKey is equal to the current MessengerKey.
            /// </summary>
            /// <param name="obj"></param>
            /// <returns></returns>
            public override bool Equals(object obj)
            {
                if (ReferenceEquals(null, obj)) return false;
                if (ReferenceEquals(this, obj)) return true;
                if (obj.GetType() != GetType()) return false;

                return Equals((MessengerKey)obj);
            }

            /// <summary>
            /// Serves as a hash function for a particular type. 
            /// </summary>
            /// <returns></returns>
            public override int GetHashCode()
            {
                unchecked
                {
                    return ((Recipient != null ? Recipient.GetHashCode() : 0) * 397) ^ (Context != null ? Context.GetHashCode() : 0);
                }
            }
        }
    }

}

状态栏虚拟机:

public class StatusBarViewModel : INotifyPropertyChanged
{
    private string _statusBarText; 
    public string StatusBarText
    {
        get => _statusBarText;
        set
        {
            _statusBarText = value;
            OnPropertyChanged(nameof(StatusBarText));
        }
    }

    public MainViewModel()
    {
        Messenger.Default.Register<UpdateStatusBar>(this, OnUpdateStatusBar);
    }

    private void OnUpdateStatusBar(UpdateStatusBar obj)
    {
        StatusBarText = obj.Text;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

留言类:

public class UpdateStatusBar
{
    public UpdateStatusBar(string text)
    {
        Text = text;
    }
    public string Text { get; set; }
}

向状态栏发送消息:

StatusBarService.WriteToStatusBar($"Exercise '{Exercise.Name}' created");

XAML(确保包含此 XAML 的视图正在使用 StatusBar VM):

<StatusBar Background="SlateBlue"
           DockPanel.Dock="Bottom">
    <StatusBarItem Content="{Binding StatusBarText}"></StatusBarItem>
</StatusBar>
于 2020-08-05T10:20:34.530 回答
-2

为您的状态栏对象应用单例模式。

于 2013-07-20T21:36:34.510 回答