MVVM:通知 属性 UI 中的更改
MVVM: Notifying property change in the UI
我正在构建一个记录器。 UI(LoggerView) 绑定到 LogItem 的三个属性:DateTime
、Status
和 Message
。当我 运行 程序时,日志记录事件确实被创建并正确地记录到 UI 中。但是,当我关闭 UI、记录更多附加事件并再次打开 UI 时,附加事件会记录到 events
但它们不会出现在 UI.
问题是当新的 LogItems
被添加到集合中时 UI 没有得到通知。添加额外的 LogItems
时如何通知 UI?
LoggerViewModel:
[Export]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.Shared)]
public class LoggerViewModel : ViewModelBase
{
protected static readonly ILog log = LogManager.GetLogger(typeof(LoggerViewModel));
private Hierarchy h = LogManager.GetRepository() as Hierarchy;
[ImportingConstructor]
public LoggerViewModel()
{
LogItems = new ObservableCollection<LogItem>();
foreach (var customLog in CustomLogger.GetLogItems())
{
var logItem = new LogItem(customLog.TimeStamp, customLog.Status, customLog.Message);
LogItems.Add(logItem);
}
}
private ObservableCollection<LogItem> _logItems;
public ObservableCollection<LogItem> LogItems
{
get { return _logItems; }
set { _logItems = value; }
}
private DateTime _timeStamp;
public DateTime TimeStamp
{
get { return _timeStamp; }
set
{
if (value == _timeStamp)
return;
_timeStamp = value;
NotifyPropertyChanged("TimeStamp");
}
}
private Level _status;
public Level Status
{
get { return _status; }
set
{
if (value == _status)
return;
_status = value;
NotifyPropertyChanged("Status");
}
}
private string _message;
public string Message
{
get { return _message; }
set
{
if (value == _message)
return;
_message = value;
NotifyPropertyChanged("Message");
}
}
}
LoggerView XAML:
<ListView Grid.Row="1" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible" VerticalContentAlignment="Bottom" ItemsSource="{Binding LogItems}">
<ListView.View>
<GridView>
<GridViewColumn Width="150" Header="Date And Time" DisplayMemberBinding="{Binding Path=DataContext.TimeStamp, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="50" Header="Status" DisplayMemberBinding="{Binding Path=DataContext.Status, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="1800" Header="Message">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" Text="{Binding Path=DataContext.Message, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
LoggerView 逻辑:
[Export]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
public partial class LoggerView : Window
{
private LoggerViewModel _viewModel;
protected static readonly ILog log = LogManager.GetLogger(typeof(LoggerView));
private Hierarchy h = LogManager.GetRepository() as Hierarchy;
[ImportingConstructor]
public LoggerView(LoggerViewModel viewModel)
{
_viewModel = viewModel;
this.DataContext = _viewModel;
InitializeComponent();
}
CustomLogger:LogItems
正在从 events
创建
public class CustomLogger
{
protected static readonly ILog log = LogManager.GetLogger(typeof(CustomLogger));
static MemoryAppender memoryAppender = new MemoryAppender();
private Hierarchy h = LogManager.GetRepository() as Hierarchy;
public CustomLogger()
{
log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(@"C:\Users\username\Documents\GitHub\MassSpecStudio\MassSpecStudio 2.0\source\MassSpecStudio\Core\app.config"));
memoryAppender = h.Root.GetAppender("MemoryAppender") as MemoryAppender;
}
public static ObservableCollection<LogItem> GetLogItems()
{
var events = memoryAppender.GetEvents();
ObservableCollection<LogItem> LogItems = new ObservableCollection<LogItem>();
foreach (LoggingEvent loggingEvent in events)
{
DateTime TimeStamp = loggingEvent.TimeStamp;
Level Status = loggingEvent.Level;
string Message = loggingEvent.RenderedMessage;
LogItems.Add(new LogItem(TimeStamp, Status, Message));
}
return LogItems;
}
}
日志项:
public class LogItem : INotifyPropertyChanged
{
private DateTime _datetime;
private Level _status;
private string _message;
public LogItem(DateTime datetime, Level status, string message)
{
this._datetime = datetime;
this._status = status;
this._message = message;
}
public enum LogLevel
{
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
Fatal = 4,
}
public DateTime TimeStamp
{
get
{
return _datetime;
}
set
{
if (_datetime != value)
{
_datetime = value;
RaisePropertyChanged("DateTime");
}
}
}
public Level Status
{
get
{
return _status;
}
set
{
if (_status != value)
{
_status = value;
RaisePropertyChanged("Status");
}
}
}
public string Message
{
get
{
return _message;
}
set
{
if (_message != value)
{
_message = value;
RaisePropertyChanged("Error");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string info)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); }
}
}
记录器菜单项:
[Export]
public partial class LoggerMenuItem : MenuItem
{
protected static readonly ILog log = LogManager.GetLogger(typeof(LoggerMenuItem));
private readonly IServiceLocator _serviceLocator;
[ImportingConstructor]
public LoggerMenuItem(IServiceLocator serviceLocator)
{
_serviceLocator = serviceLocator;
InitializeComponent();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
LoggerView window = _serviceLocator.GetInstance<LoggerView>();
window.Owner = Application.Current.MainWindow;
window.Show();
}
}
更新:
我注意到我来自 CustomLogger 的静态方法 GetLogItems()
仅在 UI 第一次打开时执行。 GetLogItems()
在 events
更新后不执行。
有没有办法通知 属性 事件发生变化?
为了解决这个问题,我认为最好让 LogItems、DateTime、Status 和 Message 的设置器在 UI 被触发时触发。 MenuItem_Click
方法负责触发 UI。是否可以在 window.Show()
之前调用 NotifyPropertyChanged
?
我认为您是在解析绑定后替换 LogItems ObservableCollection,而不是调用 NotifyPropertyChanged 来通知 UI。
尝试更改 LogItems 属性 以调用 NotifyPropertyChanged:
private ObservableCollection<LogItem> _logItems;
public ObservableCollection<LogItem> LogItems
{
get { return _logItems; }
set
{
_logItems = value;
NotifyPropertyChanged("LogItems");
}
}
我认为您需要像这样更改列:
<GridView>
<GridViewColumn Width="150" Header="Date And Time" DisplayMemberBinding="{Binding TimeStamp}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextAlignment="Center" Text="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="50" Header="Status" DisplayMemberBinding="{Binding Status}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextAlignment="Center" Text="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="1800" Header="Message" DisplayMemberBinding="{Binding Message}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" Text="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
我正在构建一个记录器。 UI(LoggerView) 绑定到 LogItem 的三个属性:DateTime
、Status
和 Message
。当我 运行 程序时,日志记录事件确实被创建并正确地记录到 UI 中。但是,当我关闭 UI、记录更多附加事件并再次打开 UI 时,附加事件会记录到 events
但它们不会出现在 UI.
问题是当新的 LogItems
被添加到集合中时 UI 没有得到通知。添加额外的 LogItems
时如何通知 UI?
LoggerViewModel:
[Export]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.Shared)]
public class LoggerViewModel : ViewModelBase
{
protected static readonly ILog log = LogManager.GetLogger(typeof(LoggerViewModel));
private Hierarchy h = LogManager.GetRepository() as Hierarchy;
[ImportingConstructor]
public LoggerViewModel()
{
LogItems = new ObservableCollection<LogItem>();
foreach (var customLog in CustomLogger.GetLogItems())
{
var logItem = new LogItem(customLog.TimeStamp, customLog.Status, customLog.Message);
LogItems.Add(logItem);
}
}
private ObservableCollection<LogItem> _logItems;
public ObservableCollection<LogItem> LogItems
{
get { return _logItems; }
set { _logItems = value; }
}
private DateTime _timeStamp;
public DateTime TimeStamp
{
get { return _timeStamp; }
set
{
if (value == _timeStamp)
return;
_timeStamp = value;
NotifyPropertyChanged("TimeStamp");
}
}
private Level _status;
public Level Status
{
get { return _status; }
set
{
if (value == _status)
return;
_status = value;
NotifyPropertyChanged("Status");
}
}
private string _message;
public string Message
{
get { return _message; }
set
{
if (value == _message)
return;
_message = value;
NotifyPropertyChanged("Message");
}
}
}
LoggerView XAML:
<ListView Grid.Row="1" ScrollViewer.HorizontalScrollBarVisibility="Visible" ScrollViewer.VerticalScrollBarVisibility="Visible" VerticalContentAlignment="Bottom" ItemsSource="{Binding LogItems}">
<ListView.View>
<GridView>
<GridViewColumn Width="150" Header="Date And Time" DisplayMemberBinding="{Binding Path=DataContext.TimeStamp, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="50" Header="Status" DisplayMemberBinding="{Binding Path=DataContext.Status, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextAlignment="Center"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="1800" Header="Message">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" Text="{Binding Path=DataContext.Message, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
LoggerView 逻辑:
[Export]
[PartCreationPolicy(System.ComponentModel.Composition.CreationPolicy.NonShared)]
public partial class LoggerView : Window
{
private LoggerViewModel _viewModel;
protected static readonly ILog log = LogManager.GetLogger(typeof(LoggerView));
private Hierarchy h = LogManager.GetRepository() as Hierarchy;
[ImportingConstructor]
public LoggerView(LoggerViewModel viewModel)
{
_viewModel = viewModel;
this.DataContext = _viewModel;
InitializeComponent();
}
CustomLogger:LogItems
正在从 events
public class CustomLogger
{
protected static readonly ILog log = LogManager.GetLogger(typeof(CustomLogger));
static MemoryAppender memoryAppender = new MemoryAppender();
private Hierarchy h = LogManager.GetRepository() as Hierarchy;
public CustomLogger()
{
log4net.Config.XmlConfigurator.Configure(new System.IO.FileInfo(@"C:\Users\username\Documents\GitHub\MassSpecStudio\MassSpecStudio 2.0\source\MassSpecStudio\Core\app.config"));
memoryAppender = h.Root.GetAppender("MemoryAppender") as MemoryAppender;
}
public static ObservableCollection<LogItem> GetLogItems()
{
var events = memoryAppender.GetEvents();
ObservableCollection<LogItem> LogItems = new ObservableCollection<LogItem>();
foreach (LoggingEvent loggingEvent in events)
{
DateTime TimeStamp = loggingEvent.TimeStamp;
Level Status = loggingEvent.Level;
string Message = loggingEvent.RenderedMessage;
LogItems.Add(new LogItem(TimeStamp, Status, Message));
}
return LogItems;
}
}
日志项:
public class LogItem : INotifyPropertyChanged
{
private DateTime _datetime;
private Level _status;
private string _message;
public LogItem(DateTime datetime, Level status, string message)
{
this._datetime = datetime;
this._status = status;
this._message = message;
}
public enum LogLevel
{
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
Fatal = 4,
}
public DateTime TimeStamp
{
get
{
return _datetime;
}
set
{
if (_datetime != value)
{
_datetime = value;
RaisePropertyChanged("DateTime");
}
}
}
public Level Status
{
get
{
return _status;
}
set
{
if (_status != value)
{
_status = value;
RaisePropertyChanged("Status");
}
}
}
public string Message
{
get
{
return _message;
}
set
{
if (_message != value)
{
_message = value;
RaisePropertyChanged("Error");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string info)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); }
}
}
记录器菜单项:
[Export]
public partial class LoggerMenuItem : MenuItem
{
protected static readonly ILog log = LogManager.GetLogger(typeof(LoggerMenuItem));
private readonly IServiceLocator _serviceLocator;
[ImportingConstructor]
public LoggerMenuItem(IServiceLocator serviceLocator)
{
_serviceLocator = serviceLocator;
InitializeComponent();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
LoggerView window = _serviceLocator.GetInstance<LoggerView>();
window.Owner = Application.Current.MainWindow;
window.Show();
}
}
更新:
我注意到我来自 CustomLogger 的静态方法 GetLogItems()
仅在 UI 第一次打开时执行。 GetLogItems()
在 events
更新后不执行。
有没有办法通知 属性 事件发生变化?
为了解决这个问题,我认为最好让 LogItems、DateTime、Status 和 Message 的设置器在 UI 被触发时触发。 MenuItem_Click
方法负责触发 UI。是否可以在 window.Show()
之前调用 NotifyPropertyChanged
?
我认为您是在解析绑定后替换 LogItems ObservableCollection,而不是调用 NotifyPropertyChanged 来通知 UI。
尝试更改 LogItems 属性 以调用 NotifyPropertyChanged:
private ObservableCollection<LogItem> _logItems;
public ObservableCollection<LogItem> LogItems
{
get { return _logItems; }
set
{
_logItems = value;
NotifyPropertyChanged("LogItems");
}
}
我认为您需要像这样更改列:
<GridView>
<GridViewColumn Width="150" Header="Date And Time" DisplayMemberBinding="{Binding TimeStamp}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextAlignment="Center" Text="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="50" Header="Status" DisplayMemberBinding="{Binding Status}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" TextAlignment="Center" Text="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Width="1800" Header="Message" DisplayMemberBinding="{Binding Message}">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock TextWrapping="WrapWithOverflow" Text="{Binding}"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>