我将我的第一个MVVM项目放在一起。我有一个StatusBar,它将从应用程序中的各种视图(UserControls)更新。每个视图都有自己的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
StatusViewModel.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
MainViewModel.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(UserControl)
<StatusBar DataContext="{Binding StatusViewModel}">
...
<w:StdTextBlock Text="{Binding StatusText, UpdateSourceTrigger=PropertyChanged}" />
Application.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
MainWindow.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>
答案 0 :(得分:1)
首先,把你的主窗口,应用程序shell,并将它改为这样的......
<DockPanel>
<sample:StatusBarControl DockPanel.Dock="Bottom" x:Name="StatusBarRegion"/>
<sample:MainContentControl DockPanel.Dock="Top" x:Name="MainContentRegion"/>
</DockPanel>
</Grid>
这将shell分区到与Prism Region Manager非常相似的区域。请注意,“内容”现在是用户控件,以及要放置在该区域中的任何其他视图。另请注意,状态栏有一个区域,但不会更改其视图或视图模型。
在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 Region Manager的替代品。每个依赖项属性设置其关联区域的数据上下文。在您的情况下,您希望更改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();
}
}
此代码创建shell并分配两个依赖项属性,这两个依赖项属性又将使用各自的视图模型填充区域。
视图模型看起来像这样......
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,这种方法可以正常工作。
答案 1 :(得分: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。 Dependency Injection或Service Location可让您轻松完成此操作,但如果您想保持简单,可以使用singleton实例访问StatusViewModel
。
当您将StatusViewModel暴露给其他类时,使用接口可能是明智之举,例如:
Public Interface IStatusService
Sub SetProgress(progress As Integer)
Function SetText(text As String) As String
End Interface
编辑:将代码更改为vb。
答案 2 :(得分:0)
为状态栏对象应用单例模式。
答案 3 :(得分:0)
您可以使用消息。摘要:
信使:
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);
}
}
}
}
}
StatusBar虚拟机:
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>