{"record":{"id":"f91b4f468a25cf1a","repo":"d2phap/ImageGlass","slug":"a-button-with-the-id-0-has-already-been-define","errorCode":null,"errorMessage":"A button with the ID '{0}' has already been defined. Please choose a different and unique ID for your button to avoid conflicts.","messagePattern":"A button with the ID '(.+?)' has already been defined\\. Please choose a different and unique ID for your button to avoid conflicts\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"warning","filePath":"v9/ImageGlass/FrmSettings.cs","lineNumber":251,"sourceCode":"            var isValid = true;\n\n            try\n            {\n                // try parsing the json\n                var btn = BHelper.ParseJson<ToolbarItemModel>(e.Data);\n\n                if (btn.Type == ToolbarItemModelType.Button)\n                {\n                    var langPath = $\"{nameof(FrmSettings)}.Toolbar._Errors\";\n                    if (string.IsNullOrWhiteSpace(btn.Id))\n                    {\n                        throw new ArgumentException(Config.Language[$\"{langPath}._ButtonIdRequired\"], nameof(btn.Id));\n                    }\n\n                    if (isCreate\n                        && Config.ToolbarButtons.Any(i => i.Id.Equals(btn.Id, StringComparison.OrdinalIgnoreCase)))\n                    {\n                        throw new ArgumentException(ZString.Format(Config.Language[$\"{langPath}._ButtonIdDuplicated\"], btn.Id), nameof(btn.Id));\n                    }\n\n                    if (string.IsNullOrEmpty(btn.OnClick.Executable))\n                    {\n                        throw new ArgumentException(Config.Language[$\"{langPath}._ButtonExecutableRequired\"], nameof(btn.OnClick.Executable));\n                    }\n                }\n            }\n            catch (Exception ex)\n            {\n                _ = Config.ShowError(this, title: Config.Language[\"_._Error\"], heading: ex.Message);\n                isValid = false;\n            }\n\n            Web2.PostWeb2Message(e.Name, BHelper.ToJson(isValid));\n        }\n        #endregion // Tab Toolbar\n","sourceCodeStart":233,"sourceCodeEnd":269,"githubUrl":"https://github.com/d2phap/ImageGlass/blob/4a3c4feceffc5a8bb5e56ba836509634aaae47a9/v9/ImageGlass/FrmSettings.cs#L233-L269","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: submitting a create with an existing Id\nvar btn = new ToolbarItemModel\n{\n    Type = ToolbarItemModelType.Button,\n    Id = \"open-notepad\",  // already exists in Config.ToolbarButtons\n    OnClick = new ToolbarClickAction { Executable = \"notepad.exe\" },\n};\n\n// after: derive a unique Id from the existing set\nvar taken = Config.ToolbarButtons.Select(b => b.Id.ToLowerInvariant()).ToHashSet();\nvar baseId = \"open-notepad\";\nvar id = baseId;\nvar n = 2;\nwhile (taken.Contains(id.ToLowerInvariant())) { id = $\"{baseId}-{n++}\"; }\n\nvar btn = new ToolbarItemModel\n{\n    Type = ToolbarItemModelType.Button,\n    Id = id,\n    OnClick = new ToolbarClickAction { Executable = \"notepad.exe\" },\n};","handlingStrategy":"validation","validationCode":"// Pre-check for a duplicate Id (create path) before submitting to the handler\nstatic bool IsUniqueIdForCreate(ToolbarItemModel btn)\n{\n    if (btn?.Type != ToolbarItemModelType.Button || string.IsNullOrWhiteSpace(btn.Id))\n        return true; // other validations handle these\n    return !Config.ToolbarButtons.Any(i => i.Id.Equals(btn.Id, StringComparison.OrdinalIgnoreCase));\n}","typeGuard":"static bool IsCreatableButton(ToolbarItemModel btn, IEnumerable<ToolbarItemModel> existing) =>\n    btn != null\n    && btn.Type == ToolbarItemModelType.Button\n    && !string.IsNullOrWhiteSpace(btn.Id)\n    && !existing.Any(i => i.Id.Equals(btn.Id, StringComparison.OrdinalIgnoreCase));","tryCatchPattern":"try\n{\n    // submit create\n}\ncatch (ArgumentException ex) when (ex.ParamName == \"btn.Id\" && ex.Message.Contains(\"already been defined\"))\n{\n    // Surface a clear 'pick a different Id' message; this is expected for duplicates.\n    isValid = false;\n}","preventionTips":["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."],"tags":["toolbar","validation","duplicate-id","settings","winforms","web2","csharp"],"backgroundTag":null,"analyzedSha":"4a3c4feceffc5a8bb5e56ba836509634aaae47a9","analyzedAt":"2026-08-13T16:58:15.523Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}