|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.ComponentModel.Design;
namespace System.Windows.Forms.Design;
internal partial class CommandSet
{
/// <internalonly/>
/// <summary>
/// We extend MenuCommand for our command set items. A command set item
/// is a menu command with an added delegate that is used to determine the
/// flags for the menu item. We have different classes of delegates here.
/// For example, many menu items may be enabled when there is at least
/// one object selected, while others are only enabled if there is more than
/// one object or if there is a primary selection.
/// </summary>
protected partial class CommandSetItem : MenuCommand
{
private readonly EventHandler _statusHandler;
private readonly IEventHandlerService _eventService;
private readonly IUIService? _uiService;
private readonly CommandSet? _commandSet;
private static readonly Dictionary<EventHandler, StatusState> s_commandStatusHash = []; // Dictionary of the command statuses we are tracking.
private bool _updatingCommand; // flag we set when we're updating the command so we don't call back on the status handler.
public CommandSetItem(CommandSet commandSet,
EventHandler statusHandler,
EventHandler invokeHandler,
CommandID id,
IUIService? uiService) : this(commandSet,
statusHandler,
invokeHandler,
id,
optimizeStatus: false,
uiService)
{
}
public CommandSetItem(CommandSet commandSet,
EventHandler statusHandler,
EventHandler invokeHandler,
CommandID id) : this(commandSet,
statusHandler,
invokeHandler,
id,
optimizeStatus: false,
uiService: null)
{
}
public CommandSetItem(CommandSet commandSet,
EventHandler statusHandler,
EventHandler invokeHandler,
CommandID id,
bool optimizeStatus) : this(commandSet,
statusHandler,
invokeHandler,
id,
optimizeStatus,
uiService: null)
{
}
/// <summary>
/// Creates a new CommandSetItem.
/// </summary>
// Per SBurke...
public CommandSetItem(CommandSet commandSet,
EventHandler statusHandler,
EventHandler invokeHandler,
CommandID id,
bool optimizeStatus,
IUIService? uiService)
: base(invokeHandler, id)
{
_uiService = uiService;
_eventService = commandSet._eventService;
_statusHandler = statusHandler;
// when we optimize, it's because status is fully based on selection.
// so what we do is only call the status handler once per selection change to prevent
// doing the same work over and over again. we do this by hashing up the command statuses
// and then filling in the results we get, so we can easily retrieve them when
// the selection hasn't changed.
//
if (optimizeStatus && statusHandler is not null)
{
// we use this as our sentinel of when we're doing this.
//
_commandSet = commandSet;
//
// UNDONE:CommandSetItem is put in a static dictionary, and CommandSetItem
// references CommandSet, CommandSet reference FormDesigner. If we don't
// remove the CommandSetItem from the static dictionary, FormDesigner is
// leaked. This demonstrates a bad design. We should not keep a static
// dictionary for all the items, instead, we should keep a dictionary per
// Designer. When designer is disposed, all command items got disposed
// automatically. However, at this time, we would pick a simple way with
// low risks to fix this.
//
// if this handler isn't already in there, add it.
//
if (!s_commandStatusHash.TryGetValue(statusHandler, out StatusState? state))
{
state = new StatusState();
s_commandStatusHash.Add(statusHandler, state);
}
state._refCount++;
}
}
/// <summary>
/// Checks if the status for this command is valid, meaning we don't need to call the status handler.
/// </summary>
private bool CommandStatusValid
{
get
{
// check to see if this is a command we have hashed up and if it's version stamp
// is the same as our current selection version.
if (_commandSet is not null && s_commandStatusHash.TryGetValue(_statusHandler, out StatusState? state))
{
if (state.SelectionVersion == _commandSet.SelectionVersion)
{
return true;
}
}
return false;
}
}
/// <summary>
/// Applies the cached status to this item.
/// </summary>
private void ApplyCachedStatus()
{
if (_commandSet is not null && s_commandStatusHash.TryGetValue(_statusHandler, out StatusState? state))
{
try
{
// set our our updating flag so it doesn't call the status handler again.
//
_updatingCommand = true;
// and push the state into this command.
//
state.ApplyState(this);
}
finally
{
_updatingCommand = false;
}
}
}
/// <summary>
/// This may be called to invoke the menu item.
/// </summary>
public override void Invoke()
{
// We allow outside parties to override the availability of particular menu commands.
//
try
{
if (_eventService is not null)
{
IMenuStatusHandler? msh = (IMenuStatusHandler?)_eventService.GetHandler(typeof(IMenuStatusHandler));
if (msh is not null && msh.OverrideInvoke(this))
{
return;
}
}
base.Invoke();
}
catch (Exception e)
{
_uiService?.ShowError(e, string.Format(SR.CommandSetError, e.Message));
if (e.IsCriticalException())
{
throw;
}
}
}
///<summary>
/// Only pass this down to the base when we're not doing the cached update.
///</summary>
protected override void OnCommandChanged(EventArgs e)
{
if (!_updatingCommand)
{
base.OnCommandChanged(e);
}
}
///<summary>
/// Saves the status for this command to the statusstate that's stored in the dictionary
/// based on our status handler delegate.
///</summary>
private void SaveCommandStatus()
{
if (_commandSet is not null)
{
// see if we need to create one of these StatusState dudes.
//
if (!s_commandStatusHash.TryGetValue(_statusHandler, out StatusState? state))
{
state = new StatusState();
}
// and save the enabled, visible, checked, and supported state.
//
state.SaveState(this, _commandSet.SelectionVersion);
}
}
/// <summary>
/// Called when the status of this command should be re-queried.
/// </summary>
public void UpdateStatus()
{
// We allow outside parties to override the availability of particular menu commands.
//
if (_eventService is not null)
{
IMenuStatusHandler? msh = (IMenuStatusHandler?)_eventService.GetHandler(typeof(IMenuStatusHandler));
if (msh is not null && msh.OverrideStatus(this))
{
return;
}
}
if (_statusHandler is not null)
{
// if we need to update our status,
// call the status handler. otherwise,
// get the cached status and push it into this
// command.
//
if (!CommandStatusValid)
{
try
{
_statusHandler(this, EventArgs.Empty);
SaveCommandStatus();
}
catch
{
}
}
else
{
ApplyCachedStatus();
}
}
}
/// <summary>
/// Remove this command item from the static dictionary to avoid leaking this object.
/// </summary>
public virtual void Dispose()
{
if (s_commandStatusHash.TryGetValue(_statusHandler, out StatusState? state))
{
state._refCount--;
if (state._refCount == 0)
{
s_commandStatusHash.Remove(_statusHandler);
}
}
}
}
}
|