【问题标题】:Cannot bind ItemsControl to ObserveableCollection无法将 ItemsControl 绑定到 ObserveableCollection
【发布时间】:2021-06-28 09:33:29
【问题描述】:

正如标题所述,我在将 ObservableCollection 绑定到 ItemsControl 时遇到问题。

MainWindow.xaml:

xmlns:ext="clr-namespace:DeConfigurator.Classes"

...

<ItemsControl ItemsSource="{Binding Source=ext:InAppSink.Logs}" Name="LogList">
  <ItemsControl.ItemTemplate>
    <DataTemplate>
       <Expander Header="{Binding Level}">
         <TextBlock TextWrapping="Wrap">
            <TextBlock.Text>
               <Binding Path="FormattedEntry" />
            </TextBlock.Text>
         </TextBlock>
       </Expander>
     </DataTemplate>
   </ItemsControl.ItemTemplate>                                    

InAppSink:

namespace DeConfigurator.Classes
{
    public class InAppSink : ILogEventSink
    {
        static readonly int MAX_ITEMS = 200;
        static readonly int TO_REMOVE = 50;
        
        public ObservableCollection<LogItem> Logs { get; set; } = new ObservableCollection<LogItem>();

        public void Emit(LogEvent logEvent)
        {
            if (logEvent == null){throw new ArgumentNullException(nameof(logEvent)); }            

            if(Logs.Count > MAX_ITEMS)
            {
                for(int i = 0; i < TO_REMOVE; ++i)
                {
                    Logs.RemoveAt(i);
                }
            }

            Logs.Add(new LogItem(logEvent));            
        }
    }

    public class LogItem
    {
        readonly ITextFormatter _textFormatter =
            new MessageTemplateTextFormatter(
                "{Timestamp:yyyy-MM-dd HH:mm:ss zzz} [{Level}] [{Method}] {Message:lj}{NewLine}{Properties:j}{NewLine}{Exception}"
        );


        public string FormattedEntry { get; set; }
        public string Level { get; set; }
        public DateTime LogDate { get; set; }

        public LogItem(LogEvent logEvent)
        {
            var renderSpace = new StringWriter();
            _textFormatter.Format(logEvent, renderSpace);
            FormattedEntry = renderSpace.ToString();

            Level = logEvent.Level.ToString();
            LogDate = logEvent.Timestamp.DateTime;

        }
    }
}

我所追求的结果是让每个日志条目显示为折叠的扩展器,其中级别和日期作为标题,格式化的日志条目作为文本内容被删除。我得到的是文本“ext:InAppSink.Logs”中的每个字符都用于创建扩展器对象。为什么这个绑定不起作用?

【问题讨论】:

    标签: wpf xaml data-binding


    【解决方案1】:

    为了绑定到对象的属性,您必须将 Binding 的 Path 设置为源属性的名称,而源对象通常通过目标对象的 DataContext 传递绑定。

    有关详细信息,请参阅 Microsoft Docs 上的Data binding overview


    所以替换

    ItemsSource="{Binding Source=ext:InAppSink.Logs}"
    

    ItemsSource="{Binding Path=Logs}"
    

    并将 InAppSink 类的实例分配给视图的 DataContext,例如在 MainWindow 构造函数中:

    private readonly InAppSink sink = new InAppSink();
    
    public MainWindow()
    {
        InitializeComponent();
        DataContext = sink;
    }
    

    Logs 属性也应该是只读的,比如

    public ObservableCollection<LogItem> Logs { get; } = new ObservableCollection<LogItem>();
    

    或触发更改通知。

    【讨论】:

    • 这很好,并且已经工作到一定程度,但我现在遇到了一个例外:这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。如果您没有意识到这也是一个 serilog 接收器。我尝试添加 logItem 时的错误。
    • 您必须确保从 UI 线程添加 Log 元素。您的问题中没有任何内容表明存在一些后台处理。在 StackOverflow 上搜索该特定错误消息,您会找到解决方案。
    • 是的,这是因为我将它用作日志记录的接收器,因此在 ui 之外创建它。无论如何我都会接受你的回答,因为我的要求已经解决了,所以谢谢。
    • 看来我必须在我的代码中添加App.Current.Dispatcher.BeginInvoke((Action)delegate () { Logs.Add(new LogItem(logEvent)); });
    • 有多种方法可以实现这一点。您还可以从 DispatcherObject 派生 InAppSink,确保在主线程中创建实例,然后使用其自己的 Dispatcher 属性。它是同一个 Dispatcher,但您不必访问 Application.Current。
    猜你喜欢
    • 2012-07-04
    • 2019-09-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-05
    • 2016-08-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多