developer tip

WPF로 로그 뷰어 구현

copycodes 2020. 10. 28. 08:12
반응형

WPF로 로그 뷰어 구현


WPF로 콘솔 로그 뷰어를 구현하는 가장 좋은 방법에 대한 조언을 구합니다.

다음 기준과 일치해야합니다.

  • 100.000+ 라인으로 빠른 스크롤
  • 일부 항목 (예 : 스택 추적)은 접을 수 있어야합니다.
  • 긴 아이템 랩
  • 목록은 다른 기준 (검색, 태그 등)으로 필터링 할 수 있습니다.
  • 마지막에 새 항목이 추가 될 때 계속 스크롤되어야합니다.
  • 라인 요소는 하이퍼 링크 및 발생 카운터와 같은 추가 형식을 포함 할 수 있습니다.

일반적으로 FireBug 및 Chrome의 콘솔 창과 같은 것을 염두에 둡니다.

나는 주변에 재생 DataGrid를 다른 항목의 높이를 처리 할 수 - - ... 때문에,하지만 많은 진전을하지 않았다 스크롤 위치는 (완전히 받아 들일 수) 스크롤 막대를 해제 한 후 업데이트됩니다.

저는 어떤 형태의 가상화가 필요하며 MVVM 패턴을 따르고 싶습니다.

어떤 도움이나 조언을 환영합니다.


이 WPF 샘플을 무료로 제공하는 대신 판매를 시작해야합니다. = P

여기에 이미지 설명 입력

  • VirtualizingStackPanel매우 우수한 성능을 제공하는 가상화 된 UI (사용 ) (200000 개 이상의 항목 포함)
  • 완전히 MVVM 친화적입니다.
  • DataTemplate각 유형의 s LogEntry. 이를 통해 원하는만큼 사용자 지정할 수 있습니다. 두 종류의 LogEntries (기본 및 중첩) 만 구현했지만 아이디어를 얻었습니다. LogEntry필요한만큼 서브 클래 싱 할 수 있습니다 . 서식있는 텍스트 나 이미지를 지원할 수도 있습니다.
  • 확장 가능한 (중첩 된) 항목.
  • 줄 바꿈.
  • 을 사용하여 필터링 등을 구현할 수 있습니다 CollectionView.
  • WPF Rocks, 내 코드를 복사하여 붙여넣고 File -> New -> WPF Application결과를 직접 확인하십시오.

    <Window x:Class="MiscSamples.LogViewer"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MiscSamples"
        Title="LogViewer" Height="500" Width="800">
    <Window.Resources>
        <Style TargetType="ItemsControl" x:Key="LogViewerStyle">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate>
                        <ScrollViewer CanContentScroll="True">
                            <ItemsPresenter/>
                        </ScrollViewer>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
    
            <Setter Property="ItemsPanel">
                <Setter.Value>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel IsItemsHost="True"/>
                    </ItemsPanelTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    
        <DataTemplate DataType="{x:Type local:LogEntry}">
            <Grid IsSharedSizeScope="True">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition SharedSizeGroup="Index" Width="Auto"/>
                    <ColumnDefinition SharedSizeGroup="Date" Width="Auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
    
                <TextBlock Text="{Binding DateTime}" Grid.Column="0"
                           FontWeight="Bold" Margin="5,0,5,0"/>
    
                <TextBlock Text="{Binding Index}" Grid.Column="1"
                           FontWeight="Bold" Margin="0,0,2,0" />
    
                <TextBlock Text="{Binding Message}" Grid.Column="2"
                           TextWrapping="Wrap"/>
            </Grid>
        </DataTemplate>
    
        <DataTemplate DataType="{x:Type local:CollapsibleLogEntry}">
            <Grid IsSharedSizeScope="True">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition SharedSizeGroup="Index" Width="Auto"/>
                    <ColumnDefinition SharedSizeGroup="Date" Width="Auto"/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>
    
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
    
                <TextBlock Text="{Binding DateTime}" Grid.Column="0"
                           FontWeight="Bold" Margin="5,0,5,0"/>
    
                <TextBlock Text="{Binding Index}" Grid.Column="1"
                           FontWeight="Bold" Margin="0,0,2,0" />
    
                <TextBlock Text="{Binding Message}" Grid.Column="2"
                           TextWrapping="Wrap"/>
    
                <ToggleButton x:Name="Expander" Grid.Row="1" Grid.Column="0"
                              VerticalAlignment="Top" Content="+" HorizontalAlignment="Right"/>
    
                <ItemsControl ItemsSource="{Binding Contents}" Style="{StaticResource LogViewerStyle}"
                              Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2"
                              x:Name="Contents" Visibility="Collapsed"/>
    
            </Grid>
            <DataTemplate.Triggers>
                <Trigger SourceName="Expander" Property="IsChecked" Value="True">
                    <Setter TargetName="Contents" Property="Visibility" Value="Visible"/>
                    <Setter TargetName="Expander" Property="Content" Value="-"/>
                </Trigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </Window.Resources>
    
    <DockPanel>
        <TextBlock Text="{Binding Count, StringFormat='{}{0} Items'}"
                   DockPanel.Dock="Top"/>
    
        <ItemsControl ItemsSource="{Binding}" Style="{StaticResource LogViewerStyle}">
            <ItemsControl.Template>
                <ControlTemplate>
                    <ScrollViewer CanContentScroll="True">
                        <ItemsPresenter/>
                    </ScrollViewer>
                </ControlTemplate>
            </ItemsControl.Template>
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel IsItemsHost="True"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </DockPanel>
    </Window>
    

Code Behind : (대부분은 예제를 지원하기위한 boileplate 일뿐입니다 (임의 항목 생성).

 public partial class LogViewer : Window
    {
        private string TestData = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
        private List<string> words;
        private int maxword;
        private int index;

        public ObservableCollection<LogEntry> LogEntries { get; set; }

        public LogViewer()
        {
            InitializeComponent();

            random = new Random();
            words = TestData.Split(' ').ToList();
            maxword = words.Count - 1;

            DataContext = LogEntries = new ObservableCollection<LogEntry>();
            Enumerable.Range(0, 200000)
                      .ToList()
                      .ForEach(x => LogEntries.Add(GetRandomEntry()));

            Timer = new Timer(x => AddRandomEntry(), null, 1000, 10);
        }

        private System.Threading.Timer Timer;
        private System.Random random;
        private void AddRandomEntry()
        {
            Dispatcher.BeginInvoke((Action) (() => LogEntries.Add(GetRandomEntry())));
        }

        private LogEntry GetRandomEntry()
        {
            if (random.Next(1,10) > 1)
            {
                return new LogEntry()
                {
                    Index = index++,
                    DateTime = DateTime.Now,
                    Message = string.Join(" ", Enumerable.Range(5, random.Next(10, 50))
                                                         .Select(x => words[random.Next(0, maxword)])),
                };
            }

            return new CollapsibleLogEntry()
                       {
                           Index = index++,
                           DateTime = DateTime.Now,
                           Message = string.Join(" ", Enumerable.Range(5, random.Next(10, 50))
                                                        .Select(x => words[random.Next(0, maxword)])),
                           Contents = Enumerable.Range(5, random.Next(5, 10))
                                                .Select(i => GetRandomEntry())
                                                .ToList()
                       };

        }
    }

데이터 항목 :

public class LogEntry: PropertyChangedBase
{
    public DateTime DateTime { get; set; }

    public int Index { get; set; }

    public string Message { get; set; }
}

public class CollapsibleLogEntry: LogEntry
{
    public List<LogEntry> Contents { get; set; }
}

PropertyChangedBase :

 public class PropertyChangedBase:INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            Application.Current.Dispatcher.BeginInvoke((Action) (() =>
                                                                     {
                                                                         PropertyChangedEventHandler handler = PropertyChanged;
                                                                         if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
                                                                     }));
        }
    }

HighCore 답변은 완벽하지만 "마지막에 새 항목이 추가되면 계속 스크롤해야합니다"라는 요구 사항이 누락 된 것 같습니다.

답변 에 따르면 다음과 같이 할 수 있습니다.

기본 ScrollViewer (DockPanel 내부)에서 이벤트를 추가합니다.

<ScrollViewer CanContentScroll="True" ScrollChanged="ScrollViewer_ScrollChanged">

이벤트 소스를 캐스팅하여 자동 스크롤을 수행합니다.

    private bool AutoScroll = true;
    private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
    {
        // User scroll event : set or unset autoscroll mode
        if (e.ExtentHeightChange == 0)
        {   // Content unchanged : user scroll event
            if ((e.Source as ScrollViewer).VerticalOffset == (e.Source as ScrollViewer).ScrollableHeight)
            {   // Scroll bar is in bottom
                // Set autoscroll mode
                AutoScroll = true;
            }
            else
            {   // Scroll bar isn't in bottom
                // Unset autoscroll mode
                AutoScroll = false;
            }
        }

        // Content scroll event : autoscroll eventually
        if (AutoScroll && e.ExtentHeightChange != 0)
        {   // Content changed and autoscroll mode set
            // Autoscroll
            (e.Source as ScrollViewer).ScrollToVerticalOffset((e.Source as ScrollViewer).ExtentHeight);
        }
    }
}

참고 URL : https://stackoverflow.com/questions/16743804/implementing-a-log-viewer-with-wpf

반응형