├── .gitattributes
├── .gitignore
├── Design.pptx
├── LICENSE.txt
├── LogViewer.sln
├── LogViewer2.jpg
├── LogViewerLib
├── LogViewer.cs
├── LogViewerLib.csproj
└── StyledString.cs
├── LogViewerSolution.jpg
├── LogViewerTest.jpg
├── LogViewerTestApp
├── App.xaml
├── App.xaml.cs
├── AssemblyInfo.cs
├── LogViewerTestApp.csproj
├── MainWindow.xaml
└── MainWindow.xaml.cs
├── Multithreading.jpg
├── Multithreading2.jpg
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
--------------------------------------------------------------------------------
/Design.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeterHuberSg/LogViewer/21daded003734554c21fd9d71f2dab7ce5d04d64/Design.pptx
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Creative Commons Legal Code
2 |
3 | CC0 1.0 Universal
4 |
5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12 | HEREUNDER.
13 |
14 | Statement of Purpose
15 |
16 | The laws of most jurisdictions throughout the world automatically confer
17 | exclusive Copyright and Related Rights (defined below) upon the creator
18 | and subsequent owner(s) (each and all, an "owner") of an original work of
19 | authorship and/or a database (each, a "Work").
20 |
21 | Certain owners wish to permanently relinquish those rights to a Work for
22 | the purpose of contributing to a commons of creative, cultural and
23 | scientific works ("Commons") that the public can reliably and without fear
24 | of later claims of infringement build upon, modify, incorporate in other
25 | works, reuse and redistribute as freely as possible in any form whatsoever
26 | and for any purposes, including without limitation commercial purposes.
27 | These owners may contribute to the Commons to promote the ideal of a free
28 | culture and the further production of creative, cultural and scientific
29 | works, or to gain reputation or greater distribution for their Work in
30 | part through the use and efforts of others.
31 |
32 | For these and/or other purposes and motivations, and without any
33 | expectation of additional consideration or compensation, the person
34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35 | is an owner of Copyright and Related Rights in the Work, voluntarily
36 | elects to apply CC0 to the Work and publicly distribute the Work under its
37 | terms, with knowledge of his or her Copyright and Related Rights in the
38 | Work and the meaning and intended legal effect of CC0 on those rights.
39 |
40 | 1. Copyright and Related Rights. A Work made available under CC0 may be
41 | protected by copyright and related or neighboring rights ("Copyright and
42 | Related Rights"). Copyright and Related Rights include, but are not
43 | limited to, the following:
44 |
45 | i. the right to reproduce, adapt, distribute, perform, display,
46 | communicate, and translate a Work;
47 | ii. moral rights retained by the original author(s) and/or performer(s);
48 | iii. publicity and privacy rights pertaining to a person's image or
49 | likeness depicted in a Work;
50 | iv. rights protecting against unfair competition in regards to a Work,
51 | subject to the limitations in paragraph 4(a), below;
52 | v. rights protecting the extraction, dissemination, use and reuse of data
53 | in a Work;
54 | vi. database rights (such as those arising under Directive 96/9/EC of the
55 | European Parliament and of the Council of 11 March 1996 on the legal
56 | protection of databases, and under any national implementation
57 | thereof, including any amended or successor version of such
58 | directive); and
59 | vii. other similar, equivalent or corresponding rights throughout the
60 | world based on applicable law or treaty, and any national
61 | implementations thereof.
62 |
63 | 2. Waiver. To the greatest extent permitted by, but not in contravention
64 | of, applicable law, Affirmer hereby overtly, fully, permanently,
65 | irrevocably and unconditionally waives, abandons, and surrenders all of
66 | Affirmer's Copyright and Related Rights and associated claims and causes
67 | of action, whether now known or unknown (including existing as well as
68 | future claims and causes of action), in the Work (i) in all territories
69 | worldwide, (ii) for the maximum duration provided by applicable law or
70 | treaty (including future time extensions), (iii) in any current or future
71 | medium and for any number of copies, and (iv) for any purpose whatsoever,
72 | including without limitation commercial, advertising or promotional
73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74 | member of the public at large and to the detriment of Affirmer's heirs and
75 | successors, fully intending that such Waiver shall not be subject to
76 | revocation, rescission, cancellation, termination, or any other legal or
77 | equitable action to disrupt the quiet enjoyment of the Work by the public
78 | as contemplated by Affirmer's express Statement of Purpose.
79 |
80 | 3. Public License Fallback. Should any part of the Waiver for any reason
81 | be judged legally invalid or ineffective under applicable law, then the
82 | Waiver shall be preserved to the maximum extent permitted taking into
83 | account Affirmer's express Statement of Purpose. In addition, to the
84 | extent the Waiver is so judged Affirmer hereby grants to each affected
85 | person a royalty-free, non transferable, non sublicensable, non exclusive,
86 | irrevocable and unconditional license to exercise Affirmer's Copyright and
87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the
88 | maximum duration provided by applicable law or treaty (including future
89 | time extensions), (iii) in any current or future medium and for any number
90 | of copies, and (iv) for any purpose whatsoever, including without
91 | limitation commercial, advertising or promotional purposes (the
92 | "License"). The License shall be deemed effective as of the date CC0 was
93 | applied by Affirmer to the Work. Should any part of the License for any
94 | reason be judged legally invalid or ineffective under applicable law, such
95 | partial invalidity or ineffectiveness shall not invalidate the remainder
96 | of the License, and in such case Affirmer hereby affirms that he or she
97 | will not (i) exercise any of his or her remaining Copyright and Related
98 | Rights in the Work or (ii) assert any associated claims and causes of
99 | action with respect to the Work, in either case contrary to Affirmer's
100 | express Statement of Purpose.
101 |
102 | 4. Limitations and Disclaimers.
103 |
104 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
105 | surrendered, licensed or otherwise affected by this document.
106 | b. Affirmer offers the Work as-is and makes no representations or
107 | warranties of any kind concerning the Work, express, implied,
108 | statutory or otherwise, including without limitation warranties of
109 | title, merchantability, fitness for a particular purpose, non
110 | infringement, or the absence of latent or other defects, accuracy, or
111 | the present or absence of errors, whether or not discoverable, all to
112 | the greatest extent permissible under applicable law.
113 | c. Affirmer disclaims responsibility for clearing rights of other persons
114 | that may apply to the Work or any use thereof, including without
115 | limitation any person's Copyright and Related Rights in the Work.
116 | Further, Affirmer disclaims responsibility for obtaining any necessary
117 | consents, permissions or other rights required for any use of the
118 | Work.
119 | d. Affirmer understands and acknowledges that Creative Commons is not a
120 | party to this document and has no duty or obligation with respect to
121 | this CC0 or use of the Work.
122 |
--------------------------------------------------------------------------------
/LogViewer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.3.32811.315
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LogViewerTestApp", "LogViewerTestApp\LogViewerTestApp.csproj", "{80DDCA3E-983D-4F88-8647-18806226254C}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LogViewerLib", "LogViewerLib\LogViewerLib.csproj", "{662906DF-9F64-48D2-BA00-F95AC09A14E9}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AB418A7C-5286-466D-A341-B0AEC9315701}"
11 | ProjectSection(SolutionItems) = preProject
12 | Design.pptx = Design.pptx
13 | LICENSE.txt = LICENSE.txt
14 | LogViewer2.jpg = LogViewer2.jpg
15 | LogViewerSolution.jpg = LogViewerSolution.jpg
16 | LogViewerTest.jpg = LogViewerTest.jpg
17 | Multithreading.jpg = Multithreading.jpg
18 | Multithreading2.jpg = Multithreading2.jpg
19 | README.md = README.md
20 | EndProjectSection
21 | EndProject
22 | Global
23 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
24 | Debug|Any CPU = Debug|Any CPU
25 | Release|Any CPU = Release|Any CPU
26 | EndGlobalSection
27 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
28 | {80DDCA3E-983D-4F88-8647-18806226254C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {80DDCA3E-983D-4F88-8647-18806226254C}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {80DDCA3E-983D-4F88-8647-18806226254C}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {80DDCA3E-983D-4F88-8647-18806226254C}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {662906DF-9F64-48D2-BA00-F95AC09A14E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {662906DF-9F64-48D2-BA00-F95AC09A14E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {662906DF-9F64-48D2-BA00-F95AC09A14E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {662906DF-9F64-48D2-BA00-F95AC09A14E9}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | GlobalSection(ExtensibilityGlobals) = postSolution
41 | SolutionGuid = {01603ED4-8E35-4543-8A5E-27E426B3ECC7}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/LogViewer2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeterHuberSg/LogViewer/21daded003734554c21fd9d71f2dab7ce5d04d64/LogViewer2.jpg
--------------------------------------------------------------------------------
/LogViewerLib/LogViewer.cs:
--------------------------------------------------------------------------------
1 | /**************************************************************************************
2 |
3 | LogViewerLib.StyledString
4 | =========================
5 |
6 | Defines text formatting supported by LogViewer
7 |
8 | Written in 2022 by Jürgpeter Huber
9 | Contact: https://github.com/PeterHuberSg/LogViewer
10 |
11 | To the extent possible under law, the author(s) have dedicated all copyright and
12 | related and neighboring rights to this software to the public domain worldwide under
13 | the Creative Commons 0 license (details see LICENSE.txt file, see also
14 | ).
15 |
16 | This software is distributed without any warranty.
17 | **************************************************************************************/
18 |
19 |
20 | using System;
21 | using System.Collections.Generic;
22 | using System.Windows;
23 | using System.Windows.Controls;
24 | using System.Windows.Documents;
25 | using System.Windows.Media;
26 | using System.Windows.Threading;
27 |
28 | namespace LogViewerLib {
29 |
30 |
31 | ///
32 | /// This WPF control displays logging information, scrolling the displayed text if there is not enough screen space.
33 | /// The text can be bold, italic, red, etc.
34 | /// Some information can be written temporarly. This kind of text gets only written to the screen when nothing else
35 | /// was written for 300 msecs and it gets overwritten by the next log text.
36 | ///
37 | public class LogViewer: RichTextBox {
38 |
39 | #region Properties
40 | // ----------
41 |
42 | ///
43 | /// How long a temporary write gets delayed before it is shown to the user
44 | ///
45 | public int TemporaryDelay {
46 | get { return (int)GetValue(TemporaryDelayProperty); }
47 | set { SetValue(TemporaryDelayProperty, value); }
48 | }
49 |
50 | // DependencyProperty definition
51 | public static readonly DependencyProperty TemporaryDelayProperty =
52 | DependencyProperty.Register("TemporaryDelay", typeof(int), typeof(LogViewer), new PropertyMetadata(300));
53 |
54 | ///
55 | /// Allows to customize date and time format. Default value is "hh:mm:ss".
56 | ///
57 | public string TimeStampFormat { get; set; } = "hh:mm:ss";
58 | #endregion
59 |
60 |
61 | #region Constructor
62 | // -----------
63 |
64 | readonly DispatcherTimer wpfTimer = new ();
65 | readonly FlowDocument flowDoc;
66 | Paragraph styledParagraph;
67 |
68 |
69 | public LogViewer() {
70 | HorizontalScrollBarVisibility = ScrollBarVisibility.Auto;
71 | VerticalScrollBarVisibility = ScrollBarVisibility.Visible;
72 | AcceptsReturn = false;
73 | IsReadOnly = true;
74 | IsUndoEnabled = false;
75 |
76 | wpfTimer.Tick += WpfTimer_Tick;
77 | wpfTimer.Interval = TimeSpan.FromMilliseconds(300);
78 | flowDoc = new FlowDocument();
79 | Document = flowDoc;
80 | styledParagraph = getEmptyParagraph();
81 | }
82 | #endregion
83 |
84 |
85 | #region Public Methods
86 | // --------------
87 |
88 | ///
89 | /// Empties the LogViwer content
90 | ///
91 | public void Clear() {
92 | flowDoc.Blocks.Clear();
93 | }
94 |
95 |
96 | public void WriteLine() {
97 | write(new StyledString("", StringStyleEnum.normal, LineHandlingEnum.endOfLine));
98 | }
99 |
100 |
101 | public void WriteLine(string line) {
102 | write(new StyledString(line, LineHandlingEnum.endOfLine));
103 | }
104 |
105 |
106 | ///
107 | /// This line gets only shown to the user when nothing else gets written for a short while and it will get overwritten
108 | /// by the next WriteXxx(). This is usefull when the user should get only few notifications per second even there
109 | /// might be many WriteTempXxx() be executed.
110 | ///
111 | public void WriteTempLine(string line) {
112 | write(new StyledString(line, LineHandlingEnum.temporaryEOL));
113 | }
114 |
115 |
116 | public void WriteLine(string line, StringStyleEnum stringStyle) {
117 | write(new StyledString(line, stringStyle, LineHandlingEnum.endOfLine));
118 | }
119 |
120 |
121 | ///
122 | /// This line gets only shown to the user when nothing else gets written for a short while and it will get overwritten
123 | /// by the next WriteXxx(). This is usefull when the user should get only few notifications per second even there
124 | /// might be many WriteTempXxx() be executed.
125 | ///
126 | public void WriteTempLine(string line, StringStyleEnum stringStyle) {
127 | write(new StyledString(line, stringStyle, LineHandlingEnum.endOfLine));
128 | }
129 |
130 |
131 | public void Write(string text) {
132 | write(new StyledString(text));
133 | }
134 |
135 |
136 | public void Write(string text, StringStyleEnum stringStyle) {
137 | write(new StyledString(text, stringStyle));
138 | }
139 |
140 |
141 | public void Write(StyledString styledString) {
142 | write(styledString);
143 | }
144 |
145 |
146 | public void Write(params StyledString[] styledStrings) {
147 | foreach (var styledString in styledStrings) {
148 | write(styledString);
149 | }
150 | }
151 | #endregion
152 |
153 |
154 | #region Eventhandlers
155 | // -------------
156 |
157 | private void WpfTimer_Tick(object? sender, EventArgs e) {
158 | List stringBuffer;
159 | lock (stringBuffers) {
160 | stringBuffer = stringBuffers[stringBufferIndex];
161 | if (stringBuffer.Count==0) {
162 | isWpfTimerActivated = false;
163 | wpfTimer.Stop();
164 | return;
165 |
166 | } else {
167 | stringBufferIndex = stringBufferIndex==0 ? 1 : 0;
168 | }
169 | }
170 | writeLog(stringBuffer);
171 | }
172 | #endregion
173 |
174 |
175 | #region Private methods
176 | // ---------------
177 |
178 | bool isWpfTimerActivated;
179 | readonly List[] stringBuffers = { new List(), new List()};
180 | int stringBufferIndex;
181 |
182 |
183 | private void write(StyledString styledString) {
184 | lock (stringBuffers) {
185 | var stringBuffer = stringBuffers[stringBufferIndex];
186 |
187 | //remove last line if it is temporary
188 | var lastStringIndex = stringBuffer.Count-1;
189 | if (lastStringIndex>=0) {
190 | if (stringBuffer[lastStringIndex].LineHandling==LineHandlingEnum.temporaryEOL) {
191 | do {
192 | stringBuffer.RemoveAt(lastStringIndex--);
193 | } while (lastStringIndex>=0 && stringBuffer[lastStringIndex].LineHandling!=LineHandlingEnum.endOfLine);
194 | }
195 | }
196 |
197 | stringBuffer.Add(styledString);
198 |
199 | if (!isWpfTimerActivated) {
200 | //nothing was written to log for some time. Write styledString immediately to log
201 | isWpfTimerActivated = true;
202 | if (CheckAccess()) {
203 | //running on wpf thread, write to log immediately, which will empty stringBuffer and start WPF timer
204 | writeLog(stringBuffer);
205 | stringBuffer.Clear();
206 | } else {
207 | //running on a different thread, start WPF timer on WPF Window thread.
208 | this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
209 | new Action(wpfTimer.Start));
210 | }
211 | }
212 | }
213 | }
214 |
215 |
216 | private void writeLog(List stringBuffer) {
217 | var needsScrollingToEnd = ExtentHeight<=ViewportHeight || //content is smaller than screen, start automatic scrolling
218 | ViewportHeight + VerticalOffset - ExtentHeight==0; //
219 |
220 | foreach (var styledString in stringBuffer) {
221 | append(styledString);
222 | }
223 | stringBuffer.Clear();
224 | if (needsScrollingToEnd) {
225 | ScrollToEnd();
226 | }
227 | wpfTimer.Start();
228 | }
229 |
230 |
231 | bool lastLineWasTemporary;
232 |
233 |
234 | private void append(StyledString styledString) {
235 | //breakup styledString into lines
236 | String[] lineStrings = styledString.String.Split(Environment.NewLine, StringSplitOptions.None);
237 | for (int lineIndex = 0; lineIndex < lineStrings.Length; lineIndex++) {
238 |
239 | //Process every line
240 | string lineString = lineStrings[lineIndex];
241 |
242 | //apply styles
243 | var inline = styledString.ToInline(styledParagraph, lineString);
244 |
245 | if (lastLineWasTemporary) {
246 | //last line was temporary. overwrite it
247 | lastLineWasTemporary = false;
248 | styledParagraph.Inlines.Clear();
249 | }
250 |
251 | //if last styledString was end of line, add a new paragraph to flow document.
252 | if (styledParagraph.Inlines.Count==0) {
253 | flowDoc.Blocks.Add(styledParagraph);
254 | if (styledString.LineHandling!=LineHandlingEnum.endOfLine || styledString.String.Length>0) {
255 | //not an empty line. Add the time
256 | Run timeRun = new(styledString.Created.ToString($"{TimeStampFormat} ")) {
257 | Foreground = Brushes.DimGray
258 | };
259 | styledParagraph.Inlines.Add(timeRun);
260 | }
261 | }
262 |
263 | //add line
264 | styledParagraph.Inlines.Add(inline);
265 |
266 | //check for end of line
267 | if (styledString.LineHandling==LineHandlingEnum.endOfLine) {
268 | styledParagraph = getEmptyParagraph();
269 | } else if (styledString.LineHandling==LineHandlingEnum.temporaryEOL) {
270 | lastLineWasTemporary = true;
271 | } else if (lineIndex < lineStrings.Length-1) {
272 | styledParagraph = getEmptyParagraph();
273 | }
274 | }
275 | }
276 |
277 |
278 | private Paragraph getEmptyParagraph() {
279 | Paragraph newParagraph = new Paragraph {
280 | Margin = new Thickness(0)
281 | };
282 | return newParagraph;
283 | }
284 | #endregion
285 |
286 | }
287 | }
288 |
--------------------------------------------------------------------------------
/LogViewerLib/LogViewerLib.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net6.0-windows
5 | enable
6 | true
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/LogViewerLib/StyledString.cs:
--------------------------------------------------------------------------------
1 | /**************************************************************************************
2 |
3 | LogViewerLib.StyledString
4 | =========================
5 |
6 | Defines text formatting supported by LogViewer
7 |
8 | Written in 2022 by Jürgpeter Huber
9 | Contact: https://github.com/PeterHuberSg/LogViewer
10 |
11 | To the extent possible under law, the author(s) have dedicated all copyright and
12 | related and neighboring rights to this software to the public domain worldwide under
13 | the Creative Commons 0 license (details see LICENSE.txt file, see also
14 | ).
15 |
16 | This software is distributed without any warranty.
17 | **************************************************************************************/
18 |
19 |
20 | using System;
21 | using System.Windows;
22 | using System.Windows.Documents;
23 | using System.Windows.Media;
24 |
25 |
26 | namespace LogViewerLib {
27 |
28 | #region Enumerations
29 | // ------------
30 |
31 | ///
32 | /// StringStyleEnum can be applied to StyledString. Works similarly like styles in HTML or Word.
33 | ///
34 | public enum StringStyleEnum {
35 | none = 0,
36 | normal,
37 | label,
38 | header1,
39 | errorHeader,
40 | errorText,
41 | stats
42 | }
43 |
44 |
45 | ///
46 | /// Does the StyledString mark the end of a line ? If temporary, the next line will overwrite the current line
47 | ///
48 | public enum LineHandlingEnum {
49 | none = 0,
50 | endOfLine,
51 | temporaryEOL,
52 | }
53 | #endregion
54 |
55 |
56 | ///
57 | /// A class to store a string together with some formatting instructions. The formatting is technology
58 | /// independent and can be used for WPF, HTML, etc.
59 | ///
60 | public class StyledString {
61 | #region Properties
62 | // ----------
63 |
64 | ///
65 | /// actual string
66 | ///
67 | public string String { get; private set; }
68 |
69 | ///
70 | /// Style to be applied to string
71 | ///
72 | public StringStyleEnum StringStyle { get; private set; }
73 |
74 | ///
75 | /// Should a new line be added after this string ? A string can actually contain
76 | /// Environment.NewLine, meaning one StyledString might cove several lines. LineHandling
77 | /// applies to the end of StyledString.
78 | ///
79 | public LineHandlingEnum LineHandling { get; private set; }
80 |
81 | ///
82 | /// time when string was created. Automatically filled in.
83 | ///
84 | public DateTime Created { get; private set; }
85 | #endregion
86 |
87 |
88 | #region Constructor
89 | // -----------
90 | ///
91 | /// constructor
92 | ///
93 | public StyledString(string newString, StringStyleEnum newStringStyle, LineHandlingEnum newLineHandling) {
94 | String = newString;
95 | StringStyle = newStringStyle;
96 | LineHandling = newLineHandling;
97 | Created = DateTime.Now;
98 | }
99 |
100 |
101 | ///
102 | /// constructor, style is normal
103 | ///
104 | public StyledString(string newStringString, LineHandlingEnum newLineHandling) :
105 | this(newStringString, StringStyleEnum.normal, newLineHandling) { }
106 |
107 |
108 | ///
109 | /// constructor, no new line needed
110 | ///
111 | public StyledString(string newStringString, StringStyleEnum newStringStyle) :
112 | this(newStringString, newStringStyle, LineHandlingEnum.none) { }
113 |
114 |
115 | ///
116 | /// constructor, style is normal, no new line needed
117 | ///
118 | public StyledString(string newStringString) :
119 | this(newStringString, StringStyleEnum.normal, LineHandlingEnum.none) { }
120 |
121 |
122 | ///
123 | /// constructor, probably for empty new line
124 | ///
125 | public StyledString(LineHandlingEnum newLineHandling) :
126 | this("", StringStyleEnum.normal, newLineHandling) { }
127 | #endregion
128 |
129 |
130 | #region Methods
131 | // -------
132 |
133 | ///
134 | /// If there is a new line at the end, it gets removed
135 | ///
136 | public static string RemoveNewLineAtEnd(string newString) {
137 | return newString.EndsWith(Environment.NewLine) ? newString.Substring(0, newString.Length-2) : newString;
138 | }
139 |
140 |
141 | override public string ToString() {
142 | return $"{StringStyle} {LineHandling} '{String}'";
143 | }
144 |
145 |
146 | static FontFamily courierNew = new FontFamily("Courier New");
147 |
148 |
149 | internal Inline ToInline(Paragraph styledParagraph, string lineString) {
150 | Inline inline;
151 | switch (StringStyle) {
152 | case StringStyleEnum.errorHeader:
153 | styledParagraph.Margin = new Thickness(0, 24, 0, 4);
154 | inline = new Bold(new Run(lineString)) {
155 | FontSize = styledParagraph.FontSize * 1.2,
156 | Foreground = Brushes.Red
157 | };
158 | break;
159 | case StringStyleEnum.errorText:
160 | inline = new Run(lineString) {
161 | Foreground = Brushes.Red
162 | };
163 | break;
164 | case StringStyleEnum.label:
165 | inline = new Run(lineString) {
166 | Foreground = Brushes.MidnightBlue
167 | };
168 | break;
169 | case StringStyleEnum.header1:
170 | styledParagraph.Margin = new Thickness(0, 24, 0, 4);
171 | inline = new Bold(new Run(lineString)) {
172 | FontSize = styledParagraph.FontSize * 1.2
173 | };
174 | break;
175 | case StringStyleEnum.stats:
176 | //00:00:01;7;47;35.144 MBytes;0; C:\Users\Peter\OneDrive\OneDriveData\BDSM
177 | var span = new Span();
178 | var partIndex = 0;
179 | var offset = 0;
180 | //duration
181 | var linePart = getPart(lineString, ref partIndex, ref offset);
182 | span.Inlines.Add(new Run(linePart + ' ') { FontFamily = courierNew });
183 | //directories
184 | linePart = getPart(lineString, ref partIndex, ref offset);
185 | span.Inlines.Add(new Run(linePart.PadLeft(6) + " dirs " ) { FontFamily = courierNew });
186 | //files
187 | linePart = getPart(lineString, ref partIndex, ref offset);
188 | span.Inlines.Add(new Run(linePart.PadLeft(6) + ' ') { FontFamily = courierNew });
189 | linePart = getPart(lineString, ref partIndex, ref offset);
190 | span.Inlines.Add(new Run(linePart.PadLeft(6) + " files ") { FontFamily = courierNew });
191 | //size
192 | linePart = getPart(lineString, ref partIndex, ref offset);
193 | span.Inlines.Add(new Run(linePart.PadLeft(16) + ' ') { FontFamily = courierNew });
194 | linePart = getPart(lineString, ref partIndex, ref offset);
195 | span.Inlines.Add(new Run(linePart.PadLeft(16) + ' ') { FontFamily = courierNew });
196 | //errors
197 | linePart = getPart(lineString, ref partIndex, ref offset);
198 | span.Inlines.Add(new Run(linePart.PadLeft(3) + " errors ") { FontFamily = courierNew });
199 | //comment
200 | linePart = lineString[offset..];
201 | span.Inlines.Add(new Run(linePart));
202 |
203 | inline = span;
204 | break;
205 | case StringStyleEnum.normal:
206 | case StringStyleEnum.none:
207 | default:
208 | inline = new Run(lineString);
209 | break;
210 | }
211 | return inline;
212 | }
213 |
214 |
215 | private string getPart(string lineString, ref int partIndex, ref int offset) {
216 | var endPos = lineString.IndexOf(";", offset);
217 | var part = lineString[offset..endPos];
218 | partIndex++;
219 | offset = endPos + 1;
220 | return part;
221 | }
222 | #endregion
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/LogViewerSolution.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeterHuberSg/LogViewer/21daded003734554c21fd9d71f2dab7ce5d04d64/LogViewerSolution.jpg
--------------------------------------------------------------------------------
/LogViewerTest.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeterHuberSg/LogViewer/21daded003734554c21fd9d71f2dab7ce5d04d64/LogViewerTest.jpg
--------------------------------------------------------------------------------
/LogViewerTestApp/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/LogViewerTestApp/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace LogViewerTestApp {
10 | ///
11 | /// Interaction logic for App.xaml
12 | ///
13 | public partial class App: Application {
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/LogViewerTestApp/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Windows;
2 |
3 | [assembly: ThemeInfo(
4 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
5 | //(used if a resource is not found in the page,
6 | // or application resource dictionaries)
7 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
8 | //(used if a resource is not found in the page,
9 | // app, or any theme specific resource dictionaries)
10 | )]
11 |
--------------------------------------------------------------------------------
/LogViewerTestApp/LogViewerTestApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | WinExe
5 | net6.0-windows
6 | enable
7 | true
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/LogViewerTestApp/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
18 |
19 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
44 |
45 |
46 |
47 |
52 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
71 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/LogViewerTestApp/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | /**************************************************************************************
2 |
3 | LogViewerTestApp.MainWindow
4 | ===========================
5 |
6 | WPF application to test the LogViewer control.
7 |
8 | Written in 2022 by Jürgpeter Huber
9 | Contact: https://github.com/PeterHuberSg/LogViewer
10 |
11 | To the extent possible under law, the author(s) have dedicated all copyright and
12 | related and neighboring rights to this software to the public domain worldwide under
13 | the Creative Commons 0 license (details see LICENSE.txt file, see also
14 | ).
15 |
16 | This software is distributed without any warranty.
17 | **************************************************************************************/
18 |
19 |
20 | using LogViewerLib;
21 | using System;
22 | using System.Threading;
23 | using System.Windows;
24 | using System.Windows.Controls;
25 | using System.Windows.Threading;
26 |
27 |
28 | namespace LogViewerTestApp {
29 |
30 | ///
31 | /// Interaction logic for MainWindow.xaml
32 | ///
33 | public partial class MainWindow: Window {
34 |
35 | readonly DispatcherTimer test3WpfTimer = new();
36 |
37 | public MainWindow() {
38 | InitializeComponent();
39 |
40 | test3WpfTimer.Tick += Test3WpfTimer_Tick;
41 | test3WpfTimer.Interval = TimeSpan.FromMilliseconds(80);
42 | Test1Button.Click += Test1Button_Click;
43 | Test2Button.Click += Test2Button_Click;
44 | Test3Button.Click += Test3Button_Click;
45 | ClearButton.Click += ClearButton_Click;
46 | }
47 |
48 |
49 | private void Test1Button_Click(object sender, RoutedEventArgs e) {
50 | TestLogViewer.WriteLine("WriteLine");
51 | TestLogViewer.WriteLine("WriteLine header1", StringStyleEnum.header1);
52 | TestLogViewer.WriteLine("WriteLine Label", StringStyleEnum.label);
53 | TestLogViewer.WriteLine("WriteLine ErrorHeader", StringStyleEnum.errorHeader);
54 | TestLogViewer.WriteLine("WriteLine errorText", StringStyleEnum.errorText);
55 | TestLogViewer.WriteLine("WriteLine errorText2", StringStyleEnum.errorText);
56 | TestLogViewer.WriteLine("WriteLine normal", StringStyleEnum.normal);
57 |
58 | TestLogViewer.Write("Word1 ");
59 | TestLogViewer.Write("errorHeader ", StringStyleEnum.errorHeader);
60 | TestLogViewer.Write("errorText ", StringStyleEnum.errorText);
61 | TestLogViewer.Write("Label ", StringStyleEnum.label);
62 | TestLogViewer.Write("header1 ", StringStyleEnum.header1);
63 | TestLogViewer.Write("normal ", StringStyleEnum.normal);
64 | TestLogViewer.WriteLine("WriteLine");
65 |
66 | TestLogViewer.WriteTempLine("tempLine1");
67 | TestLogViewer.WriteLine("overwrite tempLine1");
68 | TestLogViewer.Write("Normal Words ");
69 | TestLogViewer.WriteTempLine(" followed by tempLine2");
70 | TestLogViewer.WriteLine("overwrite tempLine2");
71 |
72 | TestLogViewer.WriteTempLine("tempLine3");
73 | }
74 |
75 |
76 | bool shouldTest1BeRunning;
77 | long test2Long;
78 |
79 |
80 | private void Test2Button_Click(object sender, RoutedEventArgs e) {
81 | if (Test2Button.IsChecked!.Value) {
82 | shouldTest1BeRunning = true;
83 | test2Long = 0;
84 | ThreadPool.QueueUserWorkItem(doTest2);
85 | } else {
86 | shouldTest1BeRunning = false;
87 | }
88 | }
89 |
90 |
91 | private void doTest2(object? state) {
92 | while (shouldTest1BeRunning) {
93 | TestLogViewer.WriteTempLine(test2Long.ToString("#,#"));
94 | test2Long++;
95 | };
96 | }
97 |
98 |
99 | int test3Int;
100 |
101 | private void Test3Button_Click(object sender, RoutedEventArgs e) {
102 | if (Test3Button.IsChecked!.Value) {
103 | test3Int = 0;
104 | test3WpfTimer.Start();
105 | } else {
106 | test3WpfTimer.Stop();
107 | }
108 | }
109 |
110 | private void Test3WpfTimer_Tick(object? sender, EventArgs e) {
111 | TestLogViewer.WriteLine("Test3: " + test3Int.ToString("#,#"));
112 | test3Int++;
113 | }
114 |
115 |
116 | private void ClearButton_Click(object sender, RoutedEventArgs e) {
117 | TestLogViewer.Clear();
118 | }
119 |
120 | private void TestLogViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) {
121 | ExtentTextBox.Text = e.ExtentHeight.ToString();
122 | ViewportTextBox.Text = e.ViewportHeight.ToString();
123 | VerticalOffsetTextBox.Text = e.VerticalOffset.ToString();
124 | DiffTextBox.Text = (e.VerticalOffset + e.ViewportHeight - e.ExtentHeight).ToString();
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/Multithreading.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeterHuberSg/LogViewer/21daded003734554c21fd9d71f2dab7ce5d04d64/Multithreading.jpg
--------------------------------------------------------------------------------
/Multithreading2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PeterHuberSg/LogViewer/21daded003734554c21fd9d71f2dab7ce5d04d64/Multithreading2.jpg
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LogViewer
2 | 
3 |
4 | *LogViewer* is a WPF control which displays logging information in a
5 | *RichTextBox*, scrolling always to the end of the content. *LogViewer*
6 | can also display temporary content, which gets overwritten as soon
7 | permanent content gets written. Use Case: A program has to process
8 | all files of a directory. In the end, only some directory statistics
9 | needs to get displayed to the user, but during the processing,
10 | *LogViewer* should display the name of each file currently
11 | processed. If several files get processed within 100
12 | milliseconds, only 1 name gets actually written to the *LogViewer*.
13 | It would not make sense to update *LogViewer* every few milliseconds,
14 | WPF would be too slow to perform so many updates and the user could
15 | anyway not see them. Instead *LogViewer* collects all information, but passes it
16 | on only every 100 milliseconds to the WPF thread. If there is now a
17 | file that needs seconds for processing, the user can actually see
18 | which file takes that long.
19 |
20 | The Write methods for *LogViewer* are multithreading safe. To keep
21 | WPF responsive, one often has to do processing on a different
22 | threat. The code of this thread does not need to know how to
23 | write bold or use different fonts. Instead, the class *StyledString*
24 | defines certain styles and the code activates them like this:
25 |
26 | ```csharp
27 | logViewer.WriteTempLine("some content temporarily displayed to user");
28 | logViewer.WriteLine("This is a Title", StringStyleEnum.header1);
29 | LogViwere.WriteLine("Some text without formatting.");
30 | ```
31 |
32 | It is recommended to just copy the 2 files *LogViewer.cs* and
33 | *StyledString.cs* into your application and then change according
34 | to your needs. Add the different formatting you need to
35 | *StringStyleEnum* in *StyledString.cs*:
36 |
37 | ```csharp
38 | public enum StringStyleEnum {
39 | none = 0,
40 | normal,
41 | label,
42 | header1,
43 | errorHeader,
44 | errorText,
45 | }
46 | ```
47 |
48 | You also need to update the *switch* statement in the method
49 | `StyledString.ToInline(Paragraph styledParagraph, string
50 |
51 | lineString, StyledString styledString)`. You need to add one
52 | *case* for every value you added to *StringStyleEnum*:
53 |
54 | ```csharp
55 | case StringStyleEnum.errorHeader:
56 | styledParagraph.Margin = new Thickness(0, 24, 0, 4);
57 | inline = new Bold(new Run(lineString));
58 | inline.FontSize = styledParagraph.FontSize * 1.2;
59 | inline.Foreground = Brushes.Red;
60 | break;
61 | ```
62 |
63 | For design details and more details see my article on CodeProject:
64 | https://www.codeproject.com/Articles/5340961/LogViewer-A-fast-WPF-Control-displaying-logging-in
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------