Хочу поделиться проектом, который может оказаться полезным тем, кто всё ещё разрабатывает/поддерживает десктопные .NET Framework приложения на WinForms.

В моей организации — как, наверное, и во многих других — среда разработки Microsoft Visual Studio оказалась под запретом, причём как её коммерческие версии, так и Community Edition. Всем было рекомендовано перейти на VS Code, которая хороша во всём, кроме полноценной поддержки WinForms-приложений. А именно - VS Code, в отличие от "обычной" Visual Studio, не имеет встроенного редактора (дизайнера) форм, без которого вёрстка сложных форм становится как минимум неудобной. Если с редактированием "code behind" файла проблем нет (Form1.cs, UserControl1.cs), то с файлом, описывающим "визуальщину" (Form1.designer.cs, UserControl1.designer.cs) - беда: в VS Code его можно править только на уровне кода, "WYSIWYG experience" тут недоступен.

Каких-либо доступных альтернатив штатному дизайнеру Microsoft Visual Studio я не нашёл: JetBrains Rider у нас тоже под запретом, SharpDevelop с 2020 года не развивается, а найденные на GitHub проекты меня не устроили по ряду причин:

  • один из авторов, вместо использования штатного класса System.ComponentModel.Design.DesignSurface, зачем-то реализовал его эмуляцию, вплоть до ручной отрисовки рамки ("selection adorner") и её "sizing grips"; ну а отказавшись от класса DesignSurface - автор был вынужден всю сериализацию/десериализацию реализовывать сам, с кучей ограничений/хардкода/костылей (в первую очередь - в виде жёсткого списка поддерживаемых контролов и их свойств); в общем - "no go", однозначно;

  • другой автор не смог решить проблему десериализации формы из кода "дизайнерского" файла (Form1.designer.cs), т.к. метод Deserialize() класса System.ComponentModel.Design.Serialization.TypeCodeDomSerializer ожидает на входе экземпляр System.CodeDom.CodeTypeDeclaration, для получения которого (из исходного кода) какие-либо "out of the box" решения отсутствуют. Например, класс Microsoft.CSharp.CSharpCodeProvider, имея работающий метод GenerateCodeFromType(CodeTypeDeclaration, TextWriter, ...), "зеркального" по смыслу (и работающего) метода - не имеет; точнее - метод-то у него есть (CodeCompileUnit Parse(TextReader)), но он не рабочий: его вызов приводит к вызову метода [этого же класса] ICodeParser CreateParser(), который помечен как [Obsolete], и тело которого содержит только "return null;". Иными словами, CSharpCodeProvider умеет играть только в одни ворота - генерировать исходный код из Code DOM, но не наоборот. В результате, этот автор решил сериализацию/десериализацию выполнять в/из XML, придумав для этого некий проприетарный формат; тоже "no go", по понятным причинам;

  • ни одно из попавшихся мне решений не имело поддержки ресурсов (.resx файлов) и event binding (создания методов-обработчиков событий непосредственно из дизайнера).

В итоге, я решил написать дизайнер форм сам, поставив себе цель использовать только нативные решения Microsoft, не изобретая велосипедов и избегая какого-либо хардкода - чтобы получить решение, максимально близкое к штатному дизайнеру Microsoft Visual Studio. Забегая вперёд - достичь этой цели получилось, использовав из сторонних решений только Highlight.js для синтаксической подсветки кода, который при желании (и только для preview) можно сгенерировать из дизайнера.

Первым делом нужно было решить задачу создания Code DOM по исходному коду .designer.cs файла - без этого не получится использовать TypeCodeDomSerializer для загрузки формы/юзерконтрола в DesignSurface. Для этого было решено использовать open source платформу .NET Compiler Platform (aka "Roslyn"), доступную в виде подгружаемого пакета Microsoft.CodeAnalysis. Был написан класс DesignerRoslynToCodeDomConverter, преобразующий исходный код в Microsoft.CodeAnalysis.SyntaxTree, затем в Microsoft.CodeAnalysis.CSharp.Syntax.CompilationUnitSyntax и затем - самая объёмная и важная часть - в System.CodeDom.CodeTypeDeclaration. Последняя часть кое-где использует эвристический подход, поскольку на построение семантической модели я не стал замахиваться, и в паре мест различать элементы кода приходится буквально по naming convention (см. метод TryCreateTypeReferenceExpression()). В случае нераспознанных конструкций - создаётся CodeSnippetStatement, чтобы не потерять код и иметь возможность диагностировать проблему и доработать преобразование. Если какие-то доработки и потребуются, то с большой вероятностью именно в классе DesignerRoslynToCodeDomConverter; отдельным челленджем (по крайней мере - для меня) будет создание семантической модели для исключения эвристического подхода. Но с учётом весьма простого "типового" синтаксиса, используемого внутри метода InitializeComponent(), пока что проблем не наблюдается.

Другой интересной задачей было определить набор сервисов, подлежащих добавлению в "service container", ассоциированный с нашей DesignSurface. В одном случае, было достаточно использовать готовый сервис (из System.ComponentModel.Design), а во всех остальных - пришлось написать свой сервис "с нуля", причём иногда было достаточно реализовать интерфейс сервиса лишь частично. Не имея документации о том, какая внутренняя логика реализована "под капотом" классов DesignerSerializationManager и TypeCodeDomSerializer (и какие именно сервисы и для каких целей там используются), приходилось брести наощупь, изучая исходные коды Microsoft, проваливаясь в них в дебаге, строя/проверяя всякие гипотезы. В итоге - получился вот такой "букет" из сервисов:

  • Экспериментальным путём было выяснено, что без сервиса System.ComponentModel.Design.Serialization.CodeDomComponentSerializationService два других работать не будут, а именно: сервис System.ComponentModel.Design.UndoEngine (которому необходимо уметь сериализовать/десериализовать состояния компонент для операций Undo и Redo) и сервис, наследующий System.ComponentModel.Design.Serialization.IDesignerSerializationService (которому необходимо уметь делать то же самое для операций Cut, Copy и Paste);

  • Сервис System.ComponentModel.Design.Serialization.INameCreationService пришлось реализовать самому (см. класс UiTools.WinForms.Designer.Core.MyNameCreationService) - без него компоненты, добавляемые на DesignSurface, не будут получать корректные имена ("button1", "button2", ...);

  • System.ComponentModel.Design.Serialization.IDesignerSerializationService тоже реализован самостоятельно (класс MyDesignerSerializationService) - именно он отвечает за операции Cut, Copy и Paste;

  • System.ComponentModel.Design.IMenuCommandService - отвечает за контекстное меню, отображаемое по щелчку ПКМ на компонентах DesignSurface; реализован в классе MyMenuCommandService;

  • System.ComponentModel.Design.UndoEngine - отвечает за операции Undo и Redo; реализован в классе MyUndoEngine;

  • System.Drawing.Design.IToolboxService - отвечает за панель "Toolbox"; львиную долю его методов/свойств оказалось возможным оставить "пустыми" или возвращающими null; реализован в классе MyToolboxService;

  • System.ComponentModel.Design.ITypeDiscoveryService - используется в некоторых компонентах/контролах для поиска требуемых типов; например - в контроле DataGridView, когда по клику на "Add column..." (в смарт-тэге "DataGridView Tasks") отображается диалог System.Windows.Forms.Design.DataGridViewAddColumnDialog, в котором комбобокс "Type" необходимо заполнить именами типов, производных от DataGridViewColumn (DataGridViewTextBoxColumn, DataGridViewCheckBoxColumn и т.д.) - для поиска этих типов "под капотом" используется, как оказалось, как раз ITypeDiscoveryService; реализован в классе MyTypeDiscoveryService;

  • System.ComponentModel.Design.IEventBindingService - без этого сервиса не получится создавать подписки на события компонент; реализован в классе MyEventBindingService;

  • System.ComponentModel.Design.IResourceService - отвечает за работу с ресурсами (.resx файлами); реализован в классе MyResourceService;

  • System.ComponentModel.Design.ITypeResolutionService - отвечает за поиск типа или сборки по имени; класс UiTools.WinForms.Designer.Core.MyTypeResolutionService, реализующий данный интерфейс - одно из самых важных мест проекта;

  • 4 сервиса, наследующих System.ComponentModel.Design.DesignerOptionService и управляющих поведением DesignSurface в части выравнивания контролов и наличия "сетки"; реализованы в классах DesignerOptionServiceSnapLines, DesignerOptionServiceGrid, DesignerOptionServiceGridWithoutSnapping и DesignerOptionServiceNoGuides.

Были и непонятные проблемы, которые приходилось решать "инвазивно", через reflection. Например, NRE, возникающее при наведении указателя мыши на контрол ToolStrip - точнее, на ToolStripTemplateNode (комбобокс с подсказкой "Type here", служащий для in-site создания новых элементов у ToolStrip). Как по мне - это явный баг в методе MiniToolStripRenderer.OnRenderArrow(), заключающийся в "потерянной" проверке на null:
if (e.Item?.DeviceDpi != ...) { previousDeviceDpi = e.Item.DeviceDpi; ... }- здесь обращение к e.Item.DeviceDpi приводит к NRE, хотя предшествующая проверка прошла нормально за счёт Elvis-оператора ("?."). Другое дело, что мне совершенно непонятна причина, по которой e.Item оказывается тут равным null; причём это имеет место только в случае включённой поддержки High DPI - когда в файле App.config есть строчка "<add key="DpiAwareness" value="PerMonitorV2"/>". Вылечить получилось с помощью грубого вмешательства через рефлексию - см. метод FixMenuStripDpiBug() в классе UiTools.WinForms.Designer.Program, если кому интересно. В итоге - NRE ушло, но "осадочек остался" (ибо понимание причины так и не появилось).

В целом, удалось создать дизайнер форм/юзерконтролов, сильно похожий на штатный дизайнер Microsoft Visual Studio, что для многих может оказаться плюсом. В нём есть ряд ограничений - см. разделы "Limitations" и "Specifics" в файле "UiTools.WinForms.Designer\README.md". И есть ряд моментов, проработанных не до конца - см. раздел "Grey area" там же. Но в целом - работает, Студию мне заменяет. Надеюсь, что кому-нибудь тоже окажется полезен. Использовать можно и как standalone приложение, и как VSIX-расширение для среды VS Code(тут рекомендуется ознакомиться с файлом "UiTools.WinForms.Designer\UiTools.WinForms.Designer.VsCodeExtension\README.md").

Репозиторий — здесь

Комментарии (22)


  1. georgeheadson
    04.03.2026 05:52

    В моей организации — как, наверное, и во многих других — среда разработки Microsoft Visual Studio оказалась под запретом

    Я, наверное, скажу какую-то глупость, но вот эти прекрасные люди, которые запретили вам Студию... по-хорошему надо, наверное, у них требовать/спрашивать теперь инструменты для работы, не?


    1. shpaker
      04.03.2026 05:52

      А мне понравилось, что vsc зато можно юзать ¯\_(ツ)_
      А как выбирают что можно, а что нельзя? Святой рандом?

      PS: по содержимому статьи вопросов нет - автор молодец :)


      1. SukhovPro
        04.03.2026 05:52

        Аргумент очень просто, vs code это открытый исходный код.

        А дальше начинается тупик, если уже и пользоваться этим преимуществом, то копировать репозитарий, проверять исходники, чистить телеметрию, вдруг найдут и собирать из исходников.

        А потом возникает вопрос исходников плагинов и их сборки.

        Никто не даст на это деньги конечно, но с точки зрения СБ они выполнили свою роль и можно получать премию за искоренение проприетарной VS.


    1. diakin
      04.03.2026 05:52

      Так они ведь дадут! А работать-то тебе..


  1. jaha33
    04.03.2026 05:52

    Логика запрета студии не очень понятна. Если из соображений безопасности, то и vscode не то чтобы хорошо. Там всякая телеметрия собирается майкрософтом. И в IDE и в плагинах. Насколько оно полноценно отключаемо, не очень понятно.

    Тогда уж используйте vscodium


    1. kenomimi
      04.03.2026 05:52

      Логика запрета студии не очень понятна.

      Скорее всего, запретом неявно заставляют уходить с винды, прекращать разработку под нее. А вообще такие требования ИБ почти всегда опираются на приказы трехбуквенных ведомств. Если посмотреть в документы предприятия, то там всегда стоят ссылки на источник.


  1. Kandimus
    04.03.2026 05:52

    Идеальное решение это Notepad++.
    Ну а если отбросить шутки, то запрет на райдер и студию конечно очень странный. Тем более, что на уровне предприятия легко обрубается телеметрия или же поднимается корпоративный РКН. Тут, у автора, видимо речь идет о демонстративных успехах по искоренению буржуинского софта из государственной сферы... Что ж, можем только посочувствовать автору. Но + в карму за плагин.


    1. AccountForHabr
      04.03.2026 05:52

      Notepad++ скорее всего тоже нельзя. В нем есть всякое


      1. Ndochp
        04.03.2026 05:52

        Вот как раз его точно не стоит. Там автор политически окрашенный, и несет эту краску в приложение.


  1. lsoft
    04.03.2026 05:52

    @a-yumashin это расширение опубликовано в visual studio marketplace? был бы рад поставить 5 звезд там.


    1. a-yumashin Автор
      04.03.2026 05:52

      Увы, не смог этого сделать. Для публикации расширения в VS Code Marketplace требуется, как я понял, Publisher Account, который тесно связан с учётной записью в Azure DevOps - и вот эту самую учётку я не смог создать (бан по локации, похоже). Может быть и есть какие-то обходные пути, но я их не нашёл.


      1. lsoft
        04.03.2026 05:52

        Возможно Вы правы, у меня там акаунт уже сто лет, жаль это слышать, так как из гитхаба Ваше расширение вряд ли получит популярность, а оно того стоит. Найдите кого-то за пределами санкционных стран, чтобы он создал акаунт и передал его Вам.

        Хорошая работа, желаю удачи.


        1. a-yumashin Автор
          04.03.2026 05:52

          Спасибо!


  1. QtRoS
    04.03.2026 05:52

    Кажется, в статье не раскрыт важный технический момент - расширения предполагается писать на TypeScript насколько я помню, а у Вас почти весь код на C#. Как оно взаимодействует с приложением? Или, например, как рисуется UI - с помощью средств на TS или есть возможность получить нечто вроде DC и рисовать напрямую из C# стандартными средствами по типу System.Drawing? Мне кажется это было бы интересно раскрыть в статье или хотя бы комментарии.


    1. a-yumashin Автор
      04.03.2026 05:52

      Ну, всего в статье не охватишь, не хотелось превращать её в лонгрид, от которого все будут шарахаться :) Кому интересны детали, не изложенные в статье - можно почитать README.md на Гитхабе (их там целых два - "общий" и отдельно про расширение). Но мне несложно ответить на Ваши вопросы и здесь: На C# написан сам дизайнер (скриншот которого вы видите в этой статье), а VSIX-расширение написано на TypeScript (по-другому, собственно, и не получится). Расширение общается с запущенной копией дизайнера через Named Pipes; если же таковой не найдено, то расширение сначала запустит дизайнер через child_process.spawn(). Насчёт того, как рисуется UI: никакой самодеятельности - всё делегировано стандартным механизмам от Microsoft, используемым, в том числе, и в штатном дизайнере Студии. Каждый WinForms-контрол имеет "дизайнер", назначенный с помощью атрибута DesignerAttribute. Например, у контрола DateTimePicker это System.Windows.Forms.Design.DateTimePickerDesigner. Как правило, это наследник типа ControlDesigner. Задача этих "дизайнеров" - адаптировать контрол для отображения на DesignSurface. Так что рисованием "по типу System.Drawing" заниматься не приходится. Можно, наверное, написать отдельную статью про то, как тут всё устроено "под капотом". Если это будет интересно многим - то могу заняться; истины "в последней инстанции" не обещаю (т.к. всё делалось, в основном, экспериментальным путём и с помощью изучения исходников), но на глубину собственного понимания - копнуть смогу.


  1. xtraroman
    04.03.2026 05:52

    VS Code задумывали как кросплатформ IDE странно туда тащить WinForms дизайнер. И странно видеть что кто то сегодня использует старичка WinForms.


    1. rPman
      04.03.2026 05:52

      а есть что то такое же производительное и по ресурсом для .net?


    1. a-yumashin Автор
      04.03.2026 05:52

      За бугром вон Кобол искоренить не могут до сих пор - вот это действительно старичок! Так что WinForms, можно сказать, ещё пацан. На нём, однако, тоже много чего написано. В первых двух абзацах я описал сценарий, в котором "тащить" WinForms дизайнер в VS Code таки имеет смысл. Не для всех этот сценарий актуален - ну и хорошо, те могут просто пролистать статью :)


      1. xtraroman
        04.03.2026 05:52

        Запретили использовать студию? Какая то надуманная причина делать велосипед. Я бы не взялся. Бегите оттуда)

        Винформс был придуман когда размеры экранов у всех были примерно одинаковые. Сегодня вашему приложению может достаться как старенький Моник, так и современный с большой плотностью пикселей.

        Design Surface в visual studio имеет много возможностей по расширению. Скорее всего в вашей реализации они сломаны.


        1. rPman
          04.03.2026 05:52

          поддержка масштабирования и разных dpi была добавлена в winforms достаточно давно, но требует .net 5 (это грустно, потому что оно не установлено с ОС по умолчанию, нужно доустанавливать)


  1. onets
    04.03.2026 05:52

    В далеком 2007 я работал в гос учреждении и мы там тоже писали многие проекты на WinForms. Интересно как у них сейчас там с запретами.


  1. Superzoos
    04.03.2026 05:52

    Автору респект, а вот запрет студии это клиника. Я бы уволился сразу из такой дурки