WPF - 用户控件 - 绑定依赖属性

WPF - UserControl - Binding Dependency Properties

我正在做一个用户控件,以便能够显示日志。我希望用户能够对 select 行进行复制和粘贴。我计划使用 RichTextBox,因为根据日志行的类型(警告、信息、错误),我会更改颜色。 这是代码:

<UserControl x:Class="WpfAppFrameLogging.UserControls.LoggerControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WpfAppFrameLogging.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <RichTextBox x:Name="RichText" 
                    IsReadOnly="True" 
                    ScrollViewer.VerticalScrollBarVisibility="Auto">
            <FlowDocument x:Name="FlowDoc">
                <FlowDocument.Resources>
                    <Style TargetType="{x:Type Paragraph}">
                        <Setter Property="Margin" Value="0"/>
                    </Style>
                </FlowDocument.Resources>
            </FlowDocument>
        </RichTextBox>
    </Grid>
</UserControl>

在那里,我想要一个 ObservableCollection 类型的依赖属性,当我的控制器中添加了一个日志行时,它会在 RichTextBox 中添加一个新段落。 看起来像这样的东西。

代码隐藏用户控件

var paragraphLog = new Paragraph(new Run(textLog));
paragraphLog.Margin = new Thickness(0, 0, 0, 0);

switch (status)
{
    case StatusLog.Error:
        paragraphLog.Foreground = Brushes.Red;
        break;

    case StatusLog.Success:
        paragraphLog.Foreground = Brushes.Green;
        break;

    case StatusLog.Warn:
        paragraphLog.Foreground = Brushes.Orange;
        break;

    case StatusLog.Info: // Useless--> I know
    default:
        paragraphLog.Foreground = Brushes.Black;
        break;
}

FlowDoc.Blocks.Add(paragraphLog);

RichText.ScrollToEnd();

Class 日志信息

    public class LogInfo
    {
        public string TextLog { get; set; }

        public StatusLog StatusLog { get; set; }
    }

    public enum StatusLog
    {
        Info, // --> Black
        Warn, // --> Orange
        Error, // --> Red
        Success // --> Green
    }

在视图中,我将使用 UserControl:

<myControls:LoggerControl AllLogsInfo="{Binding MainLogInfos, Mode=OneWay}" />

MainLogInfos

public ObservableCollection<LogInfo> MainLogInfos
{
    get { return _mainLogInfos; }
    set
    {
        if (value != _mainLogInfos)
        {
            _mainLogInfos = value;
            NotifyPropertyChanged();
        }
    }
}
private ObservableCollection<LogInfo> _mainLogInfos;


// Method :
public void LogError(string message)
{
    MainLogInfos.Add(new LogInfo() { TextLog = message, StatusLog = StatusLog.Error });
}

我仅限于 4.7.2 框架。 感谢您的帮助、想法、建议...

此示例使用带有文本框的列表框。它被设置为看起来像一个 TextBlock。 Style 用于根据 StatusLog 设置颜色。我包含了 4 条不同状态的日志消息。您可以 select 多行,我已经包含了一个用于复制 selected 文本的上下文菜单。

MainWindow.xaml

<Window x:Class="SO65185215.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SO65185215"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <ListBox ItemsSource="{Binding MainLogInfos}">
            <ListBox.Resources>
                <Style x:Key="TextBoxStyle" TargetType="TextBox">
                    <Style.Triggers>
                            <DataTrigger Binding="{Binding StatusLog}" Value="Warn">
                                <Setter Property="Foreground" Value="Orange"/>
                            </DataTrigger>
                        <DataTrigger Binding="{Binding StatusLog}" Value="Error">
                            <Setter Property="Foreground" Value="Red"/>
                        </DataTrigger>
                        <DataTrigger Binding="{Binding StatusLog}" Value="Success">
                            <Setter Property="Foreground" Value="Green"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </ListBox.Resources>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBox x:Name="tb" Text="{Binding TextLog}" 
                             BorderThickness="0" 
                             Background="Transparent" 
                             IsReadOnly="True"
                             Style="{StaticResource TextBoxStyle}">
                        <TextBox.ContextMenu>
                            <ContextMenu>
                                <MenuItem Command="ApplicationCommands.Copy"/>
                            </ContextMenu>
                        </TextBox.ContextMenu>
                    </TextBox>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

MainWindow.xaml.cs

using System.Collections.ObjectModel;
using System.Windows;

namespace SO65185215
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            DataContext = this;
            InitializeComponent();
            Loaded += MainWindow_Loaded;
        }

        public ObservableCollection<LogInfo> MainLogInfos { get; } = new ObservableCollection<LogInfo>();
        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            MainLogInfos.Add(new LogInfo() { TextLog = "log message 1", StatusLog = StatusLog.Info });
            MainLogInfos.Add(new LogInfo() { TextLog = "log message\nNext line message 2", StatusLog = StatusLog.Error });
            MainLogInfos.Add(new LogInfo() { TextLog = "log message3\nWarning", StatusLog = StatusLog.Warn });
            MainLogInfos.Add(new LogInfo() { TextLog = "log message success", StatusLog = StatusLog.Success });
        }
    }

    public class LogInfo
    {
        public string TextLog { get; set; }

        public StatusLog StatusLog { get; set; }
    }

    public enum StatusLog
    {
        Info, // --> Black
        Warn, // --> Orange
        Error, // --> Red
        Success // --> Green
    }
}

编辑:

如果您的目的是 select 多条消息,那么您可以将 ListBox 设置为 Multipel/Extended selectionMode:

<ListBox x:Name="listBox" ItemsSource="{Binding MainLogInfos}" SelectionMode="Multiple">

将上下文菜单移至列表框

    <ListBox.ContextMenu>
        <ContextMenu>
            <MenuItem Header="Copy selected" Click="CopySelected_Clicked"/>
        </ContextMenu>
    </ListBox.ContextMenu>

在 Click 事件处理程序中将 selected 文本连接到剪贴板。 观察 selectedItems 集合包含消息的顺序是 selected。

private void CopySelected_Clicked(object sender, RoutedEventArgs e)
{
    StringBuilder sb = new StringBuilder();
    foreach(LogInfo item in listBox.SelectedItems)
    {
        sb.AppendLine(item.TextLog);
    }
    if (sb.Length > 0)
        Clipboard.SetText(sb.ToString());
}

我找到了解决方案

我的LoggerControl.xaml

<UserControl x:Class="SDK.FrontOffice.CustomControl.LoggerControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:SDK.FrontOffice.CustomControl"
             xmlns:controls="http://metro.mahapps.com/winfx/xaml/controls"
             xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
             xmlns:converter="clr-namespace:SDK.FrontOffice.Converters"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    
    <Grid>
        <!--<Grid.Resources>
            <converter:TypeInfoToColorConverter x:Key="converterTypeInfo" />
        </Grid.Resources>-->

        <!--<GroupBox Header="Logs">-->
            <RichTextBox x:Name="RichText" 
                    IsReadOnly="True" 
                    ScrollViewer.VerticalScrollBarVisibility="Auto">
                <FlowDocument x:Name="FlowDoc">
                    <FlowDocument.Resources>
                        <Style TargetType="{x:Type Paragraph}">
                            <Setter Property="Margin" Value="0"/>
                        </Style>
                    </FlowDocument.Resources>
                </FlowDocument>
            </RichTextBox>
        <!--</GroupBox>-->
    </Grid>
</UserControl>

和代码隐藏

/// <summary>
/// Logique d'interaction pour LoggerControl.xaml
/// </summary>
public partial class LoggerControl : UserControl
{
        
    public LoggerControl()
    {
        InitializeComponent();
    }

    #region DependencyProperty

    public ObservableCollection<LogInfo> AllLogs
    {
        get { return (ObservableCollection<LogInfo>)GetValue(AllLogsProperty); }
        set { SetValue(AllLogsProperty, value); }
    }

    // Using a DependencyProperty as the backing store for AllLogs.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty AllLogsProperty =
        DependencyProperty.Register("AllLogs", 
                                    typeof(ObservableCollection<LogInfo>), 
                                    typeof(LoggerControl), 
                                    new PropertyMetadata(new ObservableCollection<LogInfo>(), 
                                        OnAllLogsChanged));

    private static void OnAllLogsChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        LoggerControl loggerControl = sender as LoggerControl;
        var old = e.OldValue as ObservableCollection<LogInfo>;

        if (old != null)
        {
            old.CollectionChanged -= loggerControl.OnWorkCollectionChanged;
        }

        var nouvelleValeur = e.NewValue as ObservableCollection<LogInfo>;

        if (nouvelleValeur != null)
        {
            nouvelleValeur.CollectionChanged += loggerControl.OnWorkCollectionChanged;
        }

        // Reset de la liste.
        if(old.Count > 0 && nouvelleValeur.Count == 0)
    
            loggerControl.FlowDoc.Blocks.Clear();
        }
    }

    /// <summary>
    /// Méthode quand la collection est modifiée.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnWorkCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.Action == NotifyCollectionChangedAction.Reset)
        {
            // Clear and update entire collection
            this.FlowDoc.Blocks.Clear();
        }

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            var nouvelleCollection = sender as ObservableCollection<LogInfo>;

            var derniereLigne = nouvelleCollection.Last();

            Paragraph paragraphLog = new Paragraph(new Run(derniereLigne.InformationMessage));
            paragraphLog.Margin = new Thickness(0, 0, 0, 0);

    witch (derniereLigne.StatusLog)
    
    case StatusLog.Error:
                    paragraphLog.Foreground = Brushes.Red;
        break;
    case StatusLog.Warn:
                    paragraphLog.Foreground = Brushes.Orange;
                    break;
    case StatusLog.InfoGreen:
                    paragraphLog.Foreground = Brushes.Green;
                    break;
    case StatusLog.Information:
                default:
                    paragraphLog.Foreground = Brushes.Black;
                    break;
    

            this.FlowDoc.Blocks.Add(paragraphLog);
            this.RichText.ScrollToEnd();
        }
    }
}