MVVM:通知UI中的属性更改

本文关键字:属性 通知 UI MVVM | 更新日期: 2023-09-27 18:06:54

我正在构建一个日志记录器。UI(LoggerView)绑定到LogItem的三个属性:DateTime, StatusMessage。当我运行程序时,日志事件确实被创建并正确地登录到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;
    }
}

LogItem:

    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)); }
        }
    }

LoggerMenuItem:

[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();
    }
}
更新:

我注意到我的静态方法GetLogItems()从CustomLogger只在第一次打开UI时执行。GetLogItems()events更新后不执行

是否有一种方法可以通知事件中的属性更改?

要解决这个问题,我认为最好是让LogItems, DateTime, Status和Message的setter在UI被触发时被触发。MenuItem_Click方法负责触发UI。是否可以在window.Show()之前调用NotifyPropertyChanged ?

MVVM:通知UI中的属性更改

我认为你正在替换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>