From 273c69951165d0ec7d7e141732957876594aad4a Mon Sep 17 00:00:00 2001 From: mark-sil <83427558+mark-sil@users.noreply.github.com> Date: Wed, 14 Oct 2026 06:49:55 -0400 Subject: [PATCH] Set status panel text through the window, not a property broadcast The StatusPanelRecordNumber, StatusPanelMessage and StatusPanelProgress properties carried the record number, the record message and the parser progress to the main window's status bar: a setter stored the property with the broadcast flag, the xCore Mediator queued an OnPropertyChanged item, and after the current action the window copied the property value into the panel whose id is the name without its StatusPanel prefix. Nothing else listened to those three names, and nothing read their values. The window now exposes SetStatusPanelText(panelId, text), and the setters call it with the panel id from Main.xml (RecordNumber, Message, Progress). The StatusPanel branch in XWindow.OnPropertyChanged is gone, and the three properties are not stored any more. The text appears when the setter runs instead of when the Mediator queue drains after the action; the status bar exists from window initialization, every setter runs on the UI thread, and none of them is re-entered, so no caller depended on the delay. A panel id with no panel is ignored, which also covers windows built without a status bar. DialogFilterStatus went through the same setter but is not a panel: the Concordance dialog reads it from the PropertyTable to show its own filter state. It keeps a plain property set without the broadcast, which no handler ever received. A direct call is also simpler than moving these properties to Pub/Sub with delayed execution: each setter can already reach the window, so no subscription, scope or end-of-action delivery is needed. Co-Authored-By: Claude Sonnet 5.5 --- .../Language Explorer/Configuration/Main.xml | 3 +- .../Controls/XMLViews/XmlBrowseRDEView.cs | 13 +--- Src/LexText/ParserUI/ParserListener.cs | 5 +- .../xCoreTests/XWindowStatusPanelTests.cs | 78 +++++++++++++++++++ Src/XCore/xWindow.cs | 22 +++--- Src/xWorks/GeneratedHtmlViewer.cs | 4 +- Src/xWorks/RecordClerk.cs | 14 ++-- 7 files changed, 104 insertions(+), 35 deletions(-) create mode 100644 Src/XCore/xCoreTests/XWindowStatusPanelTests.cs diff --git a/DistFiles/Language Explorer/Configuration/Main.xml b/DistFiles/Language Explorer/Configuration/Main.xml index 7aea0a296a..563dbb7ade 100644 --- a/DistFiles/Language Explorer/Configuration/Main.xml +++ b/DistFiles/Language Explorer/Configuration/Main.xml @@ -867,8 +867,7 @@ I (RandyR) brought them into this file, just to keep track of them for the time - + diff --git a/Src/Common/Controls/XMLViews/XmlBrowseRDEView.cs b/Src/Common/Controls/XMLViews/XmlBrowseRDEView.cs index 1a1a91ab77..09142f75c1 100644 --- a/Src/Common/Controls/XMLViews/XmlBrowseRDEView.cs +++ b/Src/Common/Controls/XMLViews/XmlBrowseRDEView.cs @@ -198,7 +198,6 @@ public override void OnPropertyChanged(string propName) /* * Starting up with Domains being startup tool. * currentContentControlObject - * StatusPanelProgress * * Switching away from Domains to another tool in same area. * currentContentControlParameters @@ -216,21 +215,15 @@ public override void OnPropertyChanged(string propName) * currentContentControlObject * * Switch to another domain - * StatusPanelRecordNumber - * StatusPanelMessage - * StatusPanelRecordNumber - * StatusPanelMessage * -selected * SemanticDomainList-selected * ActiveClerkSelectedObject * SelectedTreeBarNode */ - // "currentContentControlObject" occurs in two incoming contexts, - // and one switching away context. - // We only need to handle the switching away context, but how to tell them apart? - // JohnT: don't handle switching to another domain by catching StatusPanelRecordNumber; - // too unreliable. Catch that by override of RootObjectHvo below. + // "currentContentControlObject" occurs in two incoming contexts and one + // switching-away context, which cannot be told apart. Switching domains is + // caught by the RootObjectHvo override below. if (propName == "areaChoiceParameters" // Switching to another area || propName == "currentContentControlParameters") // Switching to another tool in same area { diff --git a/Src/LexText/ParserUI/ParserListener.cs b/Src/LexText/ParserUI/ParserListener.cs index 8838f03dda..ce63988264 100644 --- a/Src/LexText/ParserUI/ParserListener.cs +++ b/Src/LexText/ParserUI/ParserListener.cs @@ -216,9 +216,8 @@ private void Idle(object argument) // Now called by timer AND by Idle private void UpdateStatusPanelProgress() { - var statusMessage = ParserQueueString + " " + ParserActivityString; - m_propertyTable.SetProperty("StatusPanelProgress", statusMessage, true); - m_propertyTable.SetPropertyPersistence("StatusPanelProgress", false); + var window = m_propertyTable.GetWindow() as XWindow; + window?.SetStatusPanelText("Progress", ParserQueueString + " " + ParserActivityString); if (m_parserConnection != null) { diff --git a/Src/XCore/xCoreTests/XWindowStatusPanelTests.cs b/Src/XCore/xCoreTests/XWindowStatusPanelTests.cs new file mode 100644 index 0000000000..42af2c7dcd --- /dev/null +++ b/Src/XCore/xCoreTests/XWindowStatusPanelTests.cs @@ -0,0 +1,78 @@ +// Copyright (c) 2026 SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +using System.Threading; +using System.Windows.Forms; +using System.Xml; +using NUnit.Framework; + +namespace XCore +{ + /// + /// The status bar panel text contract of . + /// + [TestFixture] + [Apartment(ApartmentState.STA)] + public class XWindowStatusPanelTests + { + /// + /// A window whose status bar is built from a statusbar element alone, with no other UI. + /// + private sealed class StatusBarOnlyWindow : XWindow + { + public void LoadStatusBar(string windowXml) + { + var doc = new XmlDocument(); + doc.LoadXml(windowXml); + CreateStatusBar(doc.DocumentElement); + } + } + + private const string TwoPanels = + "" + + "" + + ""; + + private static string PanelText(XWindow window, string panelId) + { + return window.PropTable.GetValue(panelId).Text; + } + + [Test] + public void SetStatusPanelText_ShowsTheTextInThePanelWithThatId() + { + using (var window = new StatusBarOnlyWindow()) + { + window.LoadStatusBar(TwoPanels); + + window.SetStatusPanelText("RecordNumber", "3/12"); + + Assert.That(PanelText(window, "RecordNumber"), Is.EqualTo("3/12")); + Assert.That(PanelText(window, "Message"), Is.Empty); + } + } + + [Test] + public void SetStatusPanelText_IgnoresAnIdNoPanelHas() + { + using (var window = new StatusBarOnlyWindow()) + { + window.LoadStatusBar(TwoPanels); + + Assert.That(() => window.SetStatusPanelText("Progress", "parsing"), Throws.Nothing); + Assert.That(() => window.SetStatusPanelText(null, "parsing"), Throws.Nothing); + + Assert.That(PanelText(window, "Message"), Is.Empty); + Assert.That(PanelText(window, "RecordNumber"), Is.Empty); + } + } + + [Test] + public void SetStatusPanelText_IgnoresAWindowWithNoStatusBar() + { + using (var window = new StatusBarOnlyWindow()) + Assert.That(() => window.SetStatusPanelText("Message", "hello"), Throws.Nothing); + } + } +} diff --git a/Src/XCore/xWindow.cs b/Src/XCore/xWindow.cs index 307a4fe513..6542f2f4ed 100644 --- a/Src/XCore/xWindow.cs +++ b/Src/XCore/xWindow.cs @@ -1081,6 +1081,16 @@ protected void CreateStatusBar(XmlNode windowConfigurationNode) } } + /// + /// Shows in the status bar panel configured with the id + /// . Does nothing when no panel has that id. + /// + public void SetStatusPanelText(string panelId, string text) + { + if (panelId != null && m_statusPanels.TryGetValue(panelId, out var panel)) + panel.Text = text; + } + protected void RestoreWindowSettings(bool wasCrashDuringPreviousStartup) { string id = XmlUtils.GetAttributeValue(m_windowConfigurationNode, "settingsId"); @@ -1904,18 +1914,6 @@ public virtual void OnPropertyChanged(string name) case "BestStyleName": SynchronizedOnIdleTime(); break; - - default: - if (name.Length > 11 && name.Substring(0, 11) == "StatusPanel") - { - string panelName = name.Substring(11); - if (m_statusPanels.ContainsKey(panelName)) - { - StatusBarPanel panel = m_statusPanels[panelName]; - panel.Text = m_propertyTable.GetStringProperty(name, ""); - } - } - break; } } diff --git a/Src/xWorks/GeneratedHtmlViewer.cs b/Src/xWorks/GeneratedHtmlViewer.cs index f12e89a46e..b76bef4c39 100644 --- a/Src/xWorks/GeneratedHtmlViewer.cs +++ b/Src/xWorks/GeneratedHtmlViewer.cs @@ -772,8 +772,8 @@ public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configu m_configurationParameters = configurationParameters; mediator.AddColleague(this); - m_propertyTable.SetProperty("StatusPanelRecordNumber", "", true); - m_propertyTable.SetPropertyPersistence("StatusPanelRecordNumber", false); + // This viewer has no record list, so the record position panel shows nothing. + (m_propertyTable.GetWindow() as XWindow)?.SetStatusPanelText("RecordNumber", ""); #if notnow m_htmlControl.Browser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(Browser_DocumentCompleted); diff --git a/Src/xWorks/RecordClerk.cs b/Src/xWorks/RecordClerk.cs index 8c6370d299..f54881902f 100644 --- a/Src/xWorks/RecordClerk.cs +++ b/Src/xWorks/RecordClerk.cs @@ -74,7 +74,7 @@ public class RecordClerk : IDisposable, IxCoreColleague, IRecordListUpdater, IAn protected RecordBarHandler m_recordBarHandler; protected RecordList m_list; - private const string StatusBarRecordNumber = "StatusPanelRecordNumber"; + private const string StatusBarRecordNumber = "RecordNumber"; /// /// The record list might need access to this just to check membership of an object quickly. @@ -1867,7 +1867,7 @@ private void ResetStatusBarMessageForCurrentObject() // deleted objects don't have a cache (and other properties) so it was crashing. LT-3160, LT-3121,... msg = GetStatusBarMsgForCurrentObject(); } - ResetStatusBarPanel("StatusPanelMessage", msg); + ResetStatusBarPanel("Message", msg); } protected virtual string GetStatusBarMsgForCurrentObject() @@ -1887,10 +1887,10 @@ protected virtual string GetStatusBarMsgForCurrentObject() return msg; } - private void ResetStatusBarPanel(string panel, string msg) + private void ResetStatusBarPanel(string panelId, string text) { - m_propertyTable.SetProperty(panel, msg, true); - m_propertyTable.SetPropertyPersistence(panel, false); + var window = m_propertyTable.GetWindow() as XWindow; + window?.SetStatusPanelText(panelId, text); } /// @@ -2886,7 +2886,9 @@ protected void UpdateFilterStatusBarPanel() if (fIgnore) { // Set the value so that it can be picked up by the dialog (or whoever). - ResetStatusBarPanel("DialogFilterStatus", FilterStatusContents(m_list.Filter != null && m_list.Filter.IsUserVisible) ?? String.Empty); + m_propertyTable.SetProperty("DialogFilterStatus", + FilterStatusContents(m_list.Filter != null && m_list.Filter.IsUserVisible) ?? String.Empty, false); + m_propertyTable.SetPropertyPersistence("DialogFilterStatus", false); return; } var b = m_propertyTable.GetValue("Filter");