Ошибка 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 (фрагмент):
<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# обработчик:
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
- Стандартные команды WPF: ApplicationCommands
- Создание собственной RoutedCommand для Exit
- Настройка CommandBinding и KeyBinding
- Проверка несохраненных изменений перед закрытием
- Полный рабочий пример кода
- Источники
- Заключение
Почему возникает ошибка 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 просто замените:
<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):
public static class CustomCommands
{
public static readonly RoutedCommand ExitCommand = new RoutedCommand("Exit", typeof(Window));
}
Это регистрирует команду с именем “Exit” и типом владельца Window. Теперь в XAML:
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:
<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 ловит все попытки закрытия (включая крестик).
Добавьте флаг:
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 в команде:
private void Exit_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = !_hasUnsavedChanges; // Блокируем, если изменения
}
Как советуют на Stack Overflow, это просто и работает. Избегайте тяжелой логики в Closing — может быть XamlParseException, если окно уже закрывается (Rick Strahl’s blog).
Полный рабочий пример кода
Соберем все вместе. Создайте проект WPF в Visual Studio.
Commands.cs:
using System.Windows.Input;
namespace YourApp
{
public static class CustomCommands
{
public static readonly RoutedCommand ExitCommand = new RoutedCommand("Exit", typeof(Window));
}
}
MainWindow.xaml.cs:
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.
Источники
- Stack Overflow: CommandConverter cannot convert from System.String in WPF
- Microsoft Learn: How to: Create a RoutedCommand
- Stack Overflow: Check for unsaved change before moving to another window
- Rick Strahl’s Web Log: WPF Window Closing Errors
Заключение
Ошибка CommandConverter в WPF XAML — типичная ловушка для новичков: не используйте произвольные строки в Command, берите ApplicationCommands или RoutedCommand. Для Ctrl+W с проверкой изменений комбинируйте команду с Closing — надежно и без сюрпризов. Протестируйте на реальном проекте: добавьте флаг изменений, и ваше приложение станет по-настоящему user-friendly. Вопросы? Экспериментируйте с MVVM для масштаба.