Программирование

Ошибка CommandConverter в WPF XAML: как исправить Exit

Почему возникает XamlParseException с NotSupportedException 'CommandConverter cannot convert from System.String' в WPF при Command="Exitfoo". Исправление через ApplicationCommands или RoutedCommand, настройка Ctrl+W с проверкой несохраненных изменений в CommandBinding и KeyBinding.

Почему возникает ошибка XamlParseException с NotSupportedException ‘CommandConverter cannot convert from System.String’ при использовании Command=“Exitfoo” в CommandBinding WPF?

При настройке горячих клавиш в C# WPF приложении (Ctrl+W для закрытия окна) XAML не парсится:

Исключение:
System.Windows.Markup.XamlParseException: “Предоставление значения для "System.Windows.Baml2006.TypeConverterMarkupExtension" вызвало исключение.”: номер строки “9” и позиция в строке “10”.

Внутреннее исключение:
NotSupportedException: CommandConverter cannot convert from System.String.

XAML (фрагмент):

xml
<Window.CommandBindings>
 <CommandBinding Command="Save" Executed="Save_Handler"/>
 <CommandBinding Command="Open" Executed="OpenFile_Handler"/>
 <CommandBinding Command="Exitfoo" Executed="Exit_Handler"/>
 <CommandBinding Command="SaveAs" Executed="SaveAs_Handler"/>
</Window.CommandBindings>
<Window.InputBindings>
 <KeyBinding Command="Save" Key="S" Modifiers="Ctrl"/>
 <KeyBinding Command="Open" Key="O" Modifiers="Ctrl"/>
 <KeyBinding Command="Exitfoo" Key="W" Modifiers="Ctrl"/>
 <KeyBinding Command="SaveAs" Key="S" Modifiers="Ctrl+Shift"/>
</Window.InputBindings>

C# обработчик:

csharp
private void Exit_Handler(object sender, ExecutedRoutedEventArgs e)
{
 Exit();
}

Из-за чего возникает ошибка и как правильно настроить команду Exit (или аналогичную) для Ctrl+W с проверкой несохраненных изменений?

Ошибка CommandConverter cannot convert from System.String в WPF XAML возникает потому, что парсер команд (CommandConverter) не может преобразовать произвольную строку вроде “Exitfoo” в объект ICommand — это не стандартная команда WPF. Вместо этого используйте встроенные ApplicationCommands (типа Close для выхода) или создайте свою RoutedCommand, чтобы CommandBinding и KeyBinding работали корректно с Ctrl+W. С проверкой несохраненных изменений это решается флагом и обработчиком Closing окна — просто, надежно и без блокировок.


Содержание


Почему возникает ошибка CommandConverter в WPF XAML

Представьте: вы пишете XAML для WPF, добавляете CommandBinding с Command="Exitfoo", запускаете — и бац, XamlParseException с внутренним NotSupportedException. Что пошло не так?

Парсер XAML при обработке CommandBinding и KeyBinding вызывает CommandConverter. Этот конвертер пытается превратить строку из атрибута Command в реальный объект, реализующий ICommand. Стандартные команды вроде “Save” или “Open” распознаются автоматически — они берутся из ApplicationCommands. Но “Exitfoo”? Никогда о таком не слышали. Конвертер кидает исключение: CommandConverter cannot convert from System.String.

Это не баг вашего кода, а фича WPF. Как объясняют на Stack Overflow, строка должна точно совпадать с известной командой, иначе — краш на этапе парсинга XAML. Коротко: не придумывайте свои имена, используйте готовые или регистрируйте RoutedCommand.

А почему именно на строке 9, позиция 10? Там ваш Command="Exitfoo". Уберите его — ошибка уйдет, но функционал не заработает.


Стандартные команды WPF: ApplicationCommands

WPF из коробки дает кучу готовых команд в ApplicationCommands. Для вашего случая идеально подойдет Close — оно как раз для закрытия окон.

Вот основные:

Команда Горячая клавиша по умолчанию Что делает
Save Ctrl+S Сохранить файл
Open Ctrl+O Открыть файл
Close Alt+F4 или Ctrl+F4 Закрыть окно
SaveAs Ctrl+Shift+S Сохранить как…
New Ctrl+N Новый файл

В вашем XAML просто замените:

xml
<CommandBinding Command="{x:Static ApplicationCommands.Close}" Executed="Exit_Handler"/>
<KeyBinding Command="{x:Static ApplicationCommands.Close}" Key="W" Modifiers="Ctrl"/>

{x:Static} — это магия XAML, которая ссылается на статическое свойство. Никаких строк, никаких ошибок CommandConverter. Работает на ура.

Но Close по умолчанию просто вызовет Window.Close(). Хотите проверку изменений? Перейдем к кастомным командам.


Создание собственной RoutedCommand для Exit

Стандартные не всегда подходят — особенно с логикой вроде “а вдруг несохранено?”. Создайте RoutedCommand, как рекомендует официальная документация Microsoft.

В C# (лучше в статическом классе Commands.cs):

csharp
public static class CustomCommands
{
 public static readonly RoutedCommand ExitCommand = new RoutedCommand("Exit", typeof(Window));
}

Это регистрирует команду с именем “Exit” и типом владельца Window. Теперь в XAML:

xml
xmlns:local="clr-namespace:YourApp" <!-- добавьте в Window -->

<CommandBinding Command="{x:Static local:CustomCommands.ExitCommand}" Executed="Exit_Handler"/>
<KeyBinding Command="{x:Static local:CustomCommands.ExitCommand}" Key="W" Modifiers="Ctrl"/>

Преимущества? Полный контроль: CanExecute для проверки, Executed для логики. И никаких XamlParseException.


Настройка CommandBinding и KeyBinding

CommandBinding цепляет логику к команде, KeyBinding — клавишу. Ваш фрагмент почти верный, но с исправлениями:

Полный XAML для Window:

xml
<Window x:Class="YourApp.MainWindow"
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 xmlns:local="clr-namespace:YourApp"
 Title="MyApp" Height="450" Width="800">
 
 <Window.CommandBindings>
 <CommandBinding Command="{x:Static ApplicationCommands.Save}" Executed="Save_Handler"/>
 <CommandBinding Command="{x:Static ApplicationCommands.Open}" Executed="OpenFile_Handler"/>
 <CommandBinding Command="{x:Static local:CustomCommands.ExitCommand}" Executed="Exit_Handler" 
 CanExecute="Exit_CanExecute"/> <!-- Опционально для проверки -->
 <CommandBinding Command="{x:Static ApplicationCommands.SaveAs}" Executed="SaveAs_Handler"/>
 </Window.CommandBindings>
 
 <Window.InputBindings>
 <KeyBinding Command="{x:Static ApplicationCommands.Save}" Key="S" Modifiers="Ctrl"/>
 <KeyBinding Command="{x:Static ApplicationCommands.Open}" Key="O" Modifiers="Ctrl"/>
 <KeyBinding Command="{x:Static local:CustomCommands.ExitCommand}" Key="W" Modifiers="Ctrl"/>
 <KeyBinding Command="{x:Static ApplicationCommands.SaveAs}" Key="S" Modifiers="Ctrl+Shift"/>
 </Window.InputBindings>
 
 <!-- Ваш контент -->
</Window>

В C# добавьте пространство имен и статический класс. Готово — Ctrl+W закроет окно.


Проверка несохраненных изменений перед закрытием

Самый надежный способ — не в команде, а в событии Closing окна. Почему? Команда может сработать откуда угодно, а Closing ловит все попытки закрытия (включая крестик).

Добавьте флаг:

csharp
private bool _hasUnsavedChanges = false; // Устанавливайте true при изменениях

private void Window_Closing(object sender, CancelEventArgs e)
{
 if (_hasUnsavedChanges)
 {
 var result = MessageBox.Show("Есть несохраненные изменения. Сохранить?", "Выход", 
 MessageBoxButton.YesNoCancel);
 if (result == MessageBoxResult.Yes)
 {
 Save_Handler(null, null); // Автосейв
 return;
 }
 if (result == MessageBoxResult.Cancel)
 {
 e.Cancel = true; // Останавливаем закрытие
 return;
 }
 }
}

В XAML: Closing="Window_Closing". В команде Exit просто this.Close().

Для CanExecute в команде:

csharp
private void Exit_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
 e.CanExecute = !_hasUnsavedChanges; // Блокируем, если изменения
}

Как советуют на Stack Overflow, это просто и работает. Избегайте тяжелой логики в Closing — может быть XamlParseException, если окно уже закрывается (Rick Strahl’s blog).


Полный рабочий пример кода

Соберем все вместе. Создайте проект WPF в Visual Studio.

Commands.cs:

csharp
using System.Windows.Input;

namespace YourApp
{
 public static class CustomCommands
 {
 public static readonly RoutedCommand ExitCommand = new RoutedCommand("Exit", typeof(Window));
 }
}

MainWindow.xaml.cs:

csharp
public partial class MainWindow : Window
{
 private bool _hasUnsavedChanges = false;

 public MainWindow()
 {
 InitializeComponent();
 this.Closing += Window_Closing;
 }

 private void Save_Handler(object sender, ExecutedRoutedEventArgs e) { /* save logic */ _hasUnsavedChanges = false; }
 private void Exit_Handler(object sender, ExecutedRoutedEventArgs e) { this.Close(); }
 private void Exit_CanExecute(object sender, CanExecuteRoutedEventArgs e) { e.CanExecute = !_hasUnsavedChanges; }

 private void Window_Closing(object sender, CancelEventArgs e)
 {
 // Логика проверки выше
 }
}

Запустите — Ctrl+W спросит о изменениях. Никаких ошибок CommandConverter.


Источники

  1. Stack Overflow: CommandConverter cannot convert from System.String in WPF
  2. Microsoft Learn: How to: Create a RoutedCommand
  3. Stack Overflow: Check for unsaved change before moving to another window
  4. Rick Strahl’s Web Log: WPF Window Closing Errors

Заключение

Ошибка CommandConverter в WPF XAML — типичная ловушка для новичков: не используйте произвольные строки в Command, берите ApplicationCommands или RoutedCommand. Для Ctrl+W с проверкой изменений комбинируйте команду с Closing — надежно и без сюрпризов. Протестируйте на реальном проекте: добавьте флаг изменений, и ваше приложение станет по-настоящему user-friendly. Вопросы? Экспериментируйте с MVVM для масштаба.

Авторы
Проверено модерацией
Модерация