Neuron®
The Neuron® is the basis for the creation of open and secure federated networks for smart societies.
Loading...
Searching...
No Matches
QueryResultView.xaml.cs
1using Microsoft.Win32;
2using SkiaSharp;
3using System;
4using System.Collections.Generic;
5using System.Data;
6using System.IO;
7using System.Text;
8using System.Threading;
9using System.Threading.Tasks;
10using System.Windows;
11using System.Windows.Controls;
12using System.Windows.Data;
13using System.Windows.Markup;
14using System.Windows.Media;
15using System.Windows.Media.Imaging;
16using System.Xml;
17using System.Xml.Schema;
18using System.Xml.Xsl;
25using Waher.Events;
30
32{
36 public partial class QueryResultView : UserControl, ITabView
37 {
38 private readonly Dictionary<string, (DataTable, Column[], ListView)> tables = new Dictionary<string, (DataTable, Column[], ListView)>();
39 private readonly LinkedList<ThreadStart> guiQueue = new LinkedList<ThreadStart>();
40 private readonly LinkedList<ReportElement> elements = new LinkedList<ReportElement>();
41 private readonly TextBlock headerLabel;
42 private Node node;
43 private NodeQuery query;
44 private StackPanel currentPanel;
45
46 private QueryResultView(Node Node, NodeQuery Query, TextBlock HeaderLabel)
47 {
48 this.node = Node;
49 this.query = Query;
50 this.headerLabel = HeaderLabel;
51
53 }
54
55 public static async Task<QueryResultView> CreateAsync(Node Node, NodeQuery Query, TextBlock HeaderLabel)
56 {
57 QueryResultView Result = new QueryResultView(Node, Query, HeaderLabel);
58
59 if (!(Result.query is null))
60 {
61 Result.query.Aborted += Result.Query_Aborted;
62 Result.query.EventMessageReceived += Result.Query_EventMessageReceived;
63 Result.query.NewTitle += Result.Query_NewTitle;
64 Result.query.SectionAdded += Result.Query_SectionAdded;
65 Result.query.SectionCompleted += Result.Query_SectionCompleted;
66 Result.query.Started += Result.Query_Started;
67 Result.query.Done += Result.Query_Done;
68 Result.query.StatusMessageReceived += Result.Query_StatusMessageReceived;
69 Result.query.TableAdded += Result.Query_TableAdded;
70 Result.query.TableCompleted += Result.Query_TableCompleted;
71 Result.query.TableUpdated += Result.Query_TableUpdated;
72 Result.query.ObjectAdded += Result.Query_ObjectAdded;
73
74 await Result.query.ResumeAsync();
75 }
76
77 Result.currentPanel = Result.ReportPanel;
78
79 return Result;
80 }
81
82 public Node Node => this.node;
83 public NodeQuery Query => this.query;
84
85 private void UpdateGui(ThreadStart P)
86 {
87 bool Call;
88
89 lock (this.guiQueue)
90 {
91 Call = this.guiQueue.First is null;
92 this.guiQueue.AddLast(P);
93 }
94
95 if (Call)
96 MainWindow.UpdateGui(this.UpdateGuiSta);
97 }
98
99 private Task UpdateGuiSta()
100 {
101 ThreadStart P;
102 bool More;
103
104 do
105 {
106 lock (this.guiQueue)
107 {
108 if (this.guiQueue.First is null)
109 return Task.CompletedTask;
110
111 P = this.guiQueue.First.Value;
112 this.guiQueue.RemoveFirst();
113 More = !(this.guiQueue.First is null);
114 }
115
116 try
117 {
118 P();
119 }
120 catch (Exception ex)
121 {
122 Log.Exception(ex);
123 }
124 }
125 while (More);
126
127 return Task.CompletedTask;
128 }
129
130 private void StatusMessage(string Message)
131 {
132 this.UpdateGui(new ThreadStart(() =>
133 {
134 this.Status.Content = Message;
135 }));
136 }
137
138 private Task Query_Started(object Sender, NodeQueryEventArgs e)
139 {
140 this.StatusMessage("Execution started.");
141 return Task.CompletedTask;
142 }
143
144 private Task Query_Done(object Sender, NodeQueryEventArgs e)
145 {
146 this.StatusMessage("Execution completed.");
147 return Task.CompletedTask;
148 }
149
150 private Task Query_Aborted(object Sender, NodeQueryEventArgs e)
151 {
152 this.StatusMessage("Execution aborted.");
153 return Task.CompletedTask;
154 }
155
156 private Task Query_StatusMessageReceived(object Sender, NodeQueryStatusMessageEventArgs e)
157 {
158 this.StatusMessage(e.StatusMessage);
159 return Task.CompletedTask;
160 }
161
162 private Task Query_EventMessageReceived(object Sender, NodeQueryEventMessageEventArgs e)
163 {
164 this.UpdateGui(new ThreadStart(() =>
165 {
166 this.Add(new ReportEvent(e.EventType, e.EventLevel, e.EventMessage));
167 }));
168
169 return Task.CompletedTask;
170 }
171
172 private void Add(ReportEvent Event)
173 {
174 lock (this.elements)
175 {
176 this.elements.AddLast(Event);
177 }
178
179 Brush FgColor;
180 Brush BgColor;
181
182 switch (Event.EventType)
183 {
184 case QueryEventType.Information:
185 default:
186 FgColor = Brushes.Black;
187 BgColor = Brushes.White;
188 break;
189
190 case QueryEventType.Warning:
191 FgColor = Brushes.Black;
192 BgColor = Brushes.Yellow;
193 break;
194
195 case QueryEventType.Error:
196 FgColor = Brushes.Yellow;
197 BgColor = Brushes.Red;
198 break;
199
200 case QueryEventType.Exception:
201 FgColor = Brushes.Yellow;
202 BgColor = Brushes.DarkRed;
203 break;
204 }
205
206 this.currentPanel.Children.Add(new TextBlock()
207 {
208 Text = Event.EventMessage,
209 Margin = new Thickness(0, 0, 0, 6),
210 Foreground = FgColor,
211 Background = BgColor,
212 FontFamily = new FontFamily("Courier New")
213 });
214 }
215
216 private Task Query_NewTitle(object Sender, NodeQueryEventArgs e)
217 {
218 this.UpdateGui(new ThreadStart(() =>
219 {
220 this.headerLabel.Text = this.query.Title;
221 }));
222
223 return Task.CompletedTask;
224 }
225
226 private Task Query_SectionAdded(object Sender, NodeQuerySectionEventArgs e)
227 {
228 this.UpdateGui(new ThreadStart(() =>
229 {
230 this.Add(new ReportSectionCreated(e.Section.Header));
231 }));
232
233 return Task.CompletedTask;
234 }
235
236 private void Add(ReportSectionCreated Event)
237 {
238 lock (this.elements)
239 {
240 this.elements.AddLast(Event);
241 }
242
243 StackPanel Section = new StackPanel()
244 {
245 Margin = new Thickness(16, 8, 16, 8)
246 };
247
248 this.currentPanel.Children.Add(Section);
249 this.currentPanel = Section;
250
251 Section.Children.Add(new TextBlock()
252 {
253 Text = Event.Header,
254 FontSize = 20,
255 FontWeight = FontWeights.Bold,
256 Margin = new Thickness(0, 0, 0, 12)
257 });
258 }
259
260 private Task Query_SectionCompleted(object Sender, NodeQuerySectionEventArgs e)
261 {
262 this.UpdateGui(new ThreadStart(() =>
263 {
264 this.Add(new ReportSectionCompleted());
265 }));
266
267 return Task.CompletedTask;
268 }
269
270 private void Add(ReportSectionCompleted Event)
271 {
272 lock (this.elements)
273 {
274 this.elements.AddLast(Event);
275 }
276
277 this.currentPanel = this.currentPanel.Parent as StackPanel;
278 if (this.currentPanel is null)
279 this.currentPanel = this.ReportPanel;
280 }
281
282 private Task Query_TableAdded(object Sender, NodeQueryTableEventArgs e)
283 {
284 this.UpdateGui(new ThreadStart(() =>
285 {
288 }));
289
290 return Task.CompletedTask;
291 }
292
293 private void Add(ReportTableCreated Event)
294 {
295 lock (this.elements)
296 {
297 this.elements.AddLast(Event);
298 }
299
300 try
301 {
302 if (!this.tables.ContainsKey(Event.TableId))
303 {
304 DataTable Table = new DataTable(Event.Name);
305 GridView GridView = new GridView();
306
307 foreach (Column Column in Event.Columns)
308 {
310
311 // TODO: Alignment
312
313 GridView.Columns.Add(new GridViewColumn()
314 {
315 Header = Column.Header,
316 DisplayMemberBinding = new Binding(Column.ColumnId)
317 });
318 }
319
320 ListView TableView = new ListView()
321 {
322 ItemsSource = Table.DefaultView,
323 View = GridView
324 };
325
326 this.tables[Event.TableId] = (Table, Event.Columns, TableView);
327
328 this.currentPanel.Children.Add(TableView);
329 }
330 }
331 catch (Exception ex)
332 {
333 this.StatusMessage(ex.Message);
334 }
335 }
336
337 private Task Query_TableUpdated(object Sender, NodeQueryTableUpdatedEventArgs e)
338 {
339 this.UpdateGui(new ThreadStart(() =>
340 {
341 if (this.tables.TryGetValue(e.Table.TableDefinition.TableId, out (DataTable, Column[], ListView) P))
342 this.Add(new ReportTableRecords(e.Table.TableDefinition.TableId, e.NewRecords, P.Item2));
343 }));
344
345 return Task.CompletedTask;
346 }
347
348 private void Add(ReportTableRecords Event)
349 {
350 if (this.tables.TryGetValue(Event.TableId, out (DataTable, Column[], ListView) P))
351 {
352 lock (this.elements)
353 {
354 this.elements.AddLast(Event);
355 }
356
357 DataTable Table = P.Item1;
358 Column[] Columns = P.Item2;
360 object Obj;
361 int i, c = Columns.Length;
362 int d;
363
364 foreach (Record Record in Event.Records)
365 {
366 DataRow Row = Table.NewRow();
367
368 d = Math.Min(c, Record.Elements.Length);
369 for (i = 0; i < d; i++)
370 {
371 Obj = Record.Elements[i];
372 if (Obj is null)
373 continue;
374
375 Column = Columns[i];
376
377 if (Obj is bool b)
378 Row[Column.ColumnId] = b ? "✓" : string.Empty;
379 /*else if (Obj is SKColor) TODO
380 {
381 }*/
382 else if (Obj is double dbl)
383 {
384 if (Column.NrDecimals.HasValue)
385 Row[Column.ColumnId] = dbl.ToString("F" + Column.NrDecimals.Value.ToString());
386 else
387 Row[Column.ColumnId] = dbl.ToString();
388 }
389 else if (Obj is decimal dec)
390 {
391 if (Column.NrDecimals.HasValue)
392 Row[Column.ColumnId] = dec.ToString("F" + Column.NrDecimals.Value.ToString());
393 else
394 Row[Column.ColumnId] = dec.ToString();
395 }
396 else if (Obj is float f)
397 {
398 if (Column.NrDecimals.HasValue)
399 Row[Column.ColumnId] = f.ToString("F" + Column.NrDecimals.Value.ToString());
400 else
401 Row[Column.ColumnId] = f.ToString();
402 }
403 else if (Obj is DateTime DT)
404 {
405 if (DT.TimeOfDay == TimeSpan.Zero)
406 Row[Column.ColumnId] = DT.ToShortDateString();
407 else
408 Row[Column.ColumnId] = DT.ToShortDateString() + ", " + DT.ToLongTimeString();
409 }
410 /*else if (Obj is Image) TODO
411 {
412 }*/
413 else
414 Row[Column.ColumnId] = Obj.ToString();
415 }
416
417 Table.Rows.Add(Row);
418 }
419 }
420
421 //Table.AcceptChanges();
422 }
423
424 private Task Query_TableCompleted(object Sender, NodeQueryTableEventArgs e)
425 {
426 this.UpdateGui(new ThreadStart(() =>
427 {
429 }));
430
431 return Task.CompletedTask;
432 }
433
434 private void Add(ReportTableCompleted Event)
435 {
436 lock (this.elements)
437 {
438 this.elements.AddLast(Event);
439 }
440
441 this.tables.Remove(Event.TableId);
442 }
443
444 private Task Query_ObjectAdded(object Sender, NodeQueryObjectEventArgs e)
445 {
446 object Obj = e.Object.Object;
447 if (Obj is null)
448 return Task.CompletedTask;
449
450 this.UpdateGui(new ThreadStart(async () =>
451 {
452 try
453 {
454 await this.Add(new ReportObject(Obj, e.Object.Binary, e.Object.ContentType));
455 }
456 catch (Exception ex)
457 {
458 Log.Exception(ex);
459 }
460 }));
461
462 return Task.CompletedTask;
463 }
464
465 private async Task Add(ReportObject Event)
466 {
467 lock (this.elements)
468 {
469 this.elements.AddLast(Event);
470 }
471
472 if (Event.Object is SKImage Image)
473 {
475
476 BitmapImage BitmapImage;
477 byte[] Bin = Pixels.EncodeAsPng();
478
479 using (MemoryStream ms = new MemoryStream(Bin))
480 {
481 BitmapImage = new BitmapImage();
482 BitmapImage.BeginInit();
483 BitmapImage.CacheOption = BitmapCacheOption.OnLoad;
484 BitmapImage.StreamSource = ms;
485 BitmapImage.EndInit();
486 }
487
488 this.currentPanel.Children.Add(new Image()
489 {
490 Source = BitmapImage,
491 Width = Pixels.Width,
492 Height = Pixels.Height
493 });
494 }
495 else if (Event.Object is MarkdownDocument Markdown)
496 {
497 string Xaml = await Markdown.GenerateXAML();
498 object Parsed = XamlReader.Parse(Xaml);
499
500 if (Parsed is UIElement Element)
501 this.currentPanel.Children.Add(Element);
502 else
503 {
504 this.currentPanel.Children.Add(new TextBlock()
505 {
506 Text = Parsed?.ToString() ?? string.Empty,
507 Margin = new Thickness(0, 0, 0, 6)
508 });
509 }
510 }
511 else
512 {
513 this.currentPanel.Children.Add(new TextBlock()
514 {
515 Text = Event.Object.ToString(),
516 Margin = new Thickness(0, 0, 0, 6)
517 });
518 }
519 }
520
521 public void Dispose()
522 {
523 this.query?.Dispose();
524 this.query = null;
525 }
526
527 public void NewButton_Click(object Sender, RoutedEventArgs e)
528 {
529 this.tables.Clear();
530 this.guiQueue.Clear();
531 this.elements.Clear();
532 this.node = null;
533 this.query = null;
534 this.currentPanel = null;
535
536 this.ReportPanel.Children.Clear();
537 this.currentPanel = this.ReportPanel;
538 }
539
540 public void SaveButton_Click(object Sender, RoutedEventArgs e)
541 {
542 this.SaveAsButton_Click(Sender, e);
543 }
544
545 public void SaveAsButton_Click(object Sender, RoutedEventArgs e)
546 {
547 SaveFileDialog Dialog = new SaveFileDialog()
548 {
549 AddExtension = true,
550 CheckPathExists = true,
551 CreatePrompt = false,
552 DefaultExt = "xml",
553 Filter = "XML Files (*.xml)|*.xml|HTML Files (*.html,*.htm)|*.html,*.htm|All Files (*.*)|*.*",
554 Title = "Save report file"
555 };
556
557 bool? Result = Dialog.ShowDialog(MainWindow.FindWindow(this));
558
559 if (Result.HasValue && Result.Value)
560 {
561 try
562 {
563 if (Dialog.FilterIndex == 2)
564 {
565 StringBuilder Xml = new StringBuilder();
566 using (XmlWriter w = XmlWriter.Create(Xml, XML.WriterSettings(true, true)))
567 {
568 this.SaveAsXml(w);
569 }
570
571 string Html = XSL.Transform(Xml.ToString(), reportToHtml);
572
573 File.WriteAllText(Dialog.FileName, Html, Encoding.UTF8);
574 }
575 else
576 {
577 using (FileStream f = File.Create(Dialog.FileName))
578 {
579 using (XmlWriter w = XmlWriter.Create(f, XML.WriterSettings(true, false)))
580 {
581 this.SaveAsXml(w);
582 }
583 }
584 }
585 }
586 catch (Exception ex)
587 {
588 MessageBox.Show(MainWindow.FindWindow(this), ex.Message, "Unable to save file.", MessageBoxButton.OK, MessageBoxImage.Error);
589 }
590 }
591 }
592
593 private static readonly XslCompiledTransform reportToHtml = XSL.LoadTransform("Waher.Client.WPF.Transforms.ReportToHTML.xslt");
594 private static readonly XmlSchema schema = XSL.LoadSchema("Waher.Client.WPF.Schema.Report.xsd");
595 private const string reportNamespace = "http://waher.se/Schema/Report.xsd";
596 private const string reportRoot = "Report";
597
598 private void SaveAsXml(XmlWriter w)
599 {
600 w.WriteStartElement(reportRoot, reportNamespace);
601 w.WriteAttributeString("title", this.headerLabel.Text);
602
603 foreach (ReportElement Item in this.elements)
604 Item.ExportXml(w);
605
606 w.WriteEndElement();
607 w.Flush();
608 }
609
610 public void OpenButton_Click(object Sender, RoutedEventArgs e)
611 {
612 try
613 {
614 OpenFileDialog Dialog = new OpenFileDialog()
615 {
616 AddExtension = true,
617 CheckFileExists = true,
618 CheckPathExists = true,
619 DefaultExt = "xml",
620 Filter = "XML Files (*.xml)|*.xml|All Files (*.*)|*.*",
621 Multiselect = false,
622 ShowReadOnly = true,
623 Title = "Open report file"
624 };
625
626 bool? Result = Dialog.ShowDialog(MainWindow.FindWindow(this));
627
628 if (Result.HasValue && Result.Value)
629 {
630 XmlDocument Xml = new XmlDocument()
631 {
632 PreserveWhitespace = true
633 };
634 Xml.Load(Dialog.FileName);
635
636 this.Load(Xml, Dialog.FileName);
637 }
638 }
639 catch (Exception ex)
640 {
641 ex = Log.UnnestException(ex);
642 MessageBox.Show(ex.Message, "Unable to load file.", MessageBoxButton.OK, MessageBoxImage.Error);
643 }
644 }
645
646 public async void Load(XmlDocument Xml, string FileName)
647 {
648 try
649 {
650 XSL.Validate(FileName, Xml, reportRoot, reportNamespace, schema);
651
652 this.NewButton_Click(null, null);
653 this.headerLabel.Text = XML.Attribute(Xml.DocumentElement, "title");
654
655 Dictionary<string, Column[]> ColumnsByTableId = new Dictionary<string, Column[]>();
656
657 foreach (XmlNode N in Xml.DocumentElement.ChildNodes)
658 {
659 if (!(N is XmlElement E))
660 continue;
661
662 switch (E.LocalName)
663 {
664 case "Event":
665 this.Add(new ReportEvent(E));
666 break;
667
668 case "Object":
669 await this.Add(await ReportObject.CreateAsync(E));
670 break;
671
672 case "SectionStart":
673 this.Add(new ReportSectionCreated(E));
674 break;
675
676 case "SectionEnd":
677 this.Add(new ReportSectionCompleted());
678 break;
679
680 case "TableStart":
682 ColumnsByTableId[Table.TableId] = Table.Columns;
683
684 this.Add(Table);
685 break;
686
687 case "TableEnd":
688 this.Add(new ReportTableCompleted(E));
689 break;
690
691 case "Records":
692 this.Add(new ReportTableRecords(E, ColumnsByTableId));
693 break;
694 }
695 }
696 }
697 catch (Exception ex)
698 {
699 Log.Exception(ex);
700 }
701 }
702
703 }
704}
Interaction logic for QueryResultView.xaml
void InitializeComponent()
InitializeComponent
Abstract base class for report elements.
abstract void ExportXml(XmlWriter Output)
Exports element to XML
Contains information about a report event.
Definition: ReportEvent.cs:12
Contains information about a report object.
Definition: ReportObject.cs:13
static async Task< ReportObject > CreateAsync(XmlElement Xml)
Contains information about a report object.
Definition: ReportObject.cs:35
Interaction logic for xaml
Represents a node in a concentrator.
Definition: Node.cs:33
Contains a markdown document. This markdown document class supports original markdown,...
Helps with common XML-related tasks.
Definition: XML.cs:19
static string Attribute(XmlElement E, string Name)
Gets the value of an XML attribute.
Definition: XML.cs:914
static XmlWriterSettings WriterSettings(bool Indent, bool OmitXmlDeclaration)
Gets an XML writer settings object.
Definition: XML.cs:1177
Static class managing loading of XSL resources stored as embedded resources or in content files.
Definition: XSL.cs:15
static XmlSchema LoadSchema(string ResourceName)
Loads an XML schema from an embedded resource.
Definition: XSL.cs:23
static XslCompiledTransform LoadTransform(string ResourceName)
Loads an XSL transformation from an embedded resource.
Definition: XSL.cs:70
static string Transform(string XML, XslCompiledTransform Transform)
Transforms an XML document using an XSL transform.
Definition: XSL.cs:162
static void Validate(string ObjectID, XmlDocument Xml, params XmlSchema[] Schemas)
Validates an XML document given a set of XML schemas.
Definition: XSL.cs:118
Class representing an event.
Definition: Event.cs:10
string Object
Object related to the event.
Definition: Event.cs:136
Static class managing the application event log. Applications and services log events on this static ...
Definition: Log.cs:13
static void Exception(Exception Exception, string Object, string Actor, string EventId, EventLevel Level, string Facility, string Module, params KeyValuePair< string, object >[] Tags)
Logs an exception. Event type will be determined by the severity of the exception.
Definition: Log.cs:1647
static Exception UnnestException(Exception Exception)
Unnests an exception, to extract the relevant inner exception.
Definition: Log.cs:818
Client-side Node Query object. It collects the results of the query.
Definition: NodeQuery.cs:19
async Task ResumeAsync()
Resumes a paused query reception.
Definition: NodeQuery.cs:163
byte[] Binary
Binary representation of object.
Definition: QueryObject.cs:37
Contains pixel information
virtual byte[] EncodeAsPng()
Encodes the pixels into a binary PNG image.
static PixelInformation FromImage(SKImage Image)
Gets the pixel information from an SKImage.
Defines a column in a table.
Definition: Column.cs:30
string Header
Optional localized header.
Definition: Column.cs:72
string ColumnId
Column ID
Definition: Column.cs:67
byte? NrDecimals
Optional Number of Decimals.
Definition: Column.cs:102
Class handling the reception of data from a query.
Definition: Query.cs:12
Defines a record in a table.
Definition: Record.cs:9
object[] Elements
Record elements.
Definition: Record.cs:32
Represents a table in a query result.
Definition: Table.cs:7
Column[] Columns
Columns
Definition: Table.cs:38
string Name
Table name
Definition: Table.cs:33
string TableId
Table ID
Definition: Table.cs:28
Interface for tab view user controls in the client.
Definition: ITabView.cs:10
QueryEventType
Query event type.