d2phap/ImageGlass · warning · ArgumentException
A button with the ID '{0}' has already been defined. Please
Error message
A button with the ID '{0}' has already been defined. Please choose a different and unique ID for your button to avoid conflicts. What it means
Thrown in FrmSettings (Tab Toolbar handler) during button creation (isCreate == true) when the submitted ToolbarItemModel.Id already matches an existing Config.ToolbarButtons entry, case-insensitively. It is an ArgumentException whose message is built with ZString.Format from the localized resource FrmSettings.Toolbar._Errors._ButtonIdDuplicated, with {0} replaced by the offending Id. The exception is caught by the surrounding try/catch and shown via Config.ShowError, and the validation result posted back is false.
Source
Thrown at v9/ImageGlass/FrmSettings.cs:251
var isValid = true;
try
{
// try parsing the json
var btn = BHelper.ParseJson<ToolbarItemModel>(e.Data);
if (btn.Type == ToolbarItemModelType.Button)
{
var langPath = $"{nameof(FrmSettings)}.Toolbar._Errors";
if (string.IsNullOrWhiteSpace(btn.Id))
{
throw new ArgumentException(Config.Language[$"{langPath}._ButtonIdRequired"], nameof(btn.Id));
}
if (isCreate
&& Config.ToolbarButtons.Any(i => i.Id.Equals(btn.Id, StringComparison.OrdinalIgnoreCase)))
{
throw new ArgumentException(ZString.Format(Config.Language[$"{langPath}._ButtonIdDuplicated"], btn.Id), nameof(btn.Id));
}
if (string.IsNullOrEmpty(btn.OnClick.Executable))
{
throw new ArgumentException(Config.Language[$"{langPath}._ButtonExecutableRequired"], nameof(btn.OnClick.Executable));
}
}
}
catch (Exception ex)
{
_ = Config.ShowError(this, title: Config.Language["_._Error"], heading: ex.Message);
isValid = false;
}
Web2.PostWeb2Message(e.Name, BHelper.ToJson(isValid));
}
#endregion // Tab Toolbar
View on GitHub (pinned to 4a3c4fecef)
Solutions
- Choose a new, case-insensitively unique Id for the button being created.
- On edit (isCreate == false) reuse the existing Id instead of treating it as a new create.
- Add client-side dedupe: fetch the current toolbar IDs and disable submit when the Id collides.
- Normalize Ids to lowercase slugs at authoring time so casing collisions cannot occur.
Example fix
// before: submitting a create with an existing Id
var btn = new ToolbarItemModel
{
Type = ToolbarItemModelType.Button,
Id = "open-notepad", // already exists in Config.ToolbarButtons
OnClick = new ToolbarClickAction { Executable = "notepad.exe" },
};
// after: derive a unique Id from the existing set
var taken = Config.ToolbarButtons.Select(b => b.Id.ToLowerInvariant()).ToHashSet();
var baseId = "open-notepad";
var id = baseId;
var n = 2;
while (taken.Contains(id.ToLowerInvariant())) { id = $"{baseId}-{n++}"; }
var btn = new ToolbarItemModel
{
Type = ToolbarItemModelType.Button,
Id = id,
OnClick = new ToolbarClickAction { Executable = "notepad.exe" },
}; Defensive patterns
Strategy: validation
Validate before calling
// Pre-check for a duplicate Id (create path) before submitting to the handler
static bool IsUniqueIdForCreate(ToolbarItemModel btn)
{
if (btn?.Type != ToolbarItemModelType.Button || string.IsNullOrWhiteSpace(btn.Id))
return true; // other validations handle these
return !Config.ToolbarButtons.Any(i => i.Id.Equals(btn.Id, StringComparison.OrdinalIgnoreCase));
} Type guard
static bool IsCreatableButton(ToolbarItemModel btn, IEnumerable<ToolbarItemModel> existing) =>
btn != null
&& btn.Type == ToolbarItemModelType.Button
&& !string.IsNullOrWhiteSpace(btn.Id)
&& !existing.Any(i => i.Id.Equals(btn.Id, StringComparison.OrdinalIgnoreCase)); Try / catch
try
{
// submit create
}
catch (ArgumentException ex) when (ex.ParamName == "btn.Id" && ex.Message.Contains("already been defined"))
{
// Surface a clear 'pick a different Id' message; this is expected for duplicates.
isValid = false;
} Prevention
- On the create path, fetch existing IDs and disable submit when the Id collides (case-insensitive).
- Normalize Ids to lowercase slugs so casing cannot create accidental duplicates.
- On edit, reuse the existing Id instead of issuing a create.
- When importing a toolbar set, dedupe or suffix Ids before re-adding.
When it happens
Trigger: The Web2 'Btn_AddCustomToolbarButton_ValidateJson_Create' message submits a button whose Id equals (case-insensitive) the Id of any entry already in Config.ToolbarButtons; e.g. re-adding a button after a refresh without changing the Id.
Common situations: A user duplicates an existing button config and forgets to rename the Id; the Id uses different casing of an existing Id ('Open' vs 'open'); or a re-import of a previously saved toolbar set is treated as a create.
Related errors
- Button ID required.
- Button executable required.
- Wallpaper style is not valid.
- Cannot remove the default theme pack.
- IGE: Could not parse merged config.
AI-assisted analysis of d2phap/ImageGlass@4a3c4fecef (2026-08-13).
Data as JSON: /api/errors/f91b4f468a25cf1a.
Report an issue: GitHub.