2010-07-08 2 views
1

Я получаю исключение в моей C#/NET приложение, которое гласит:
CommandConverter - Действительно исключение или .NET Bug

'CommandCoverter' не в состоянии преобразовать 'MyNamespace.MyDerivedFromICommandSubclass' в «системе. Строка».

Что я делаю это довольно прямо вперед, как описано в документации MSDN ICommand:

public class MyDerivedFromICommandSubclass : ICommand 
{ 
    // Implement interface 
    ... 
} 

У меня есть FlowDocument с Hyperlink на нем. Гиперссылке разрешено иметь свойство Command, которое я установил для своей производной ICommand, чтобы при нажатии ссылки было выполнено мое пользовательское действие.

Эта часть работает.

Вот где я попадаю в неприятности: если я выберу гиперссылку и щелкните правой кнопкой мыши Копировать (или нажмите Control-C).

Мгновенно платформа .NET генерирует исключение System.NotSupportedException с подробным описанием выше. Трассировки стека показывает:

на System.ComponentModel.TypeConverter.GetConvertToException (значение объекта, тип DestinationType)
в System.Windows.Input.CommandConverter.ConvertTo (ITypeDescriptorContext контекст, CultureInfo культуры, значение объекта, тип DestinationType)

в этот момент я прибег к Red Gate's free .NET Reflector и смотрел на исходном коде для ConvertTo:

public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) 
{ 
    if (destinationType == null) // We know it isn't, it's System.String 
    { 
    throw new ArgumentNullException("destinationType"); // We don't see this exception 
    } 
    if (destinationType == typeof(string)) // It is, so control passes in 
    { 
    if (value == null) // It isn't, so this condition is skipped 
    { 
     return string.Empty; // Confirmed we don't get this return value 
    } 
    RoutedCommand command = value as RoutedCommand; 
    if (((command != null) && (command.OwnerType != null) && IsKnownType(command.OwnerType)) 
    { // Is a user-defined ICommand a known type? Doubtful. This gets skipped. 
     return command.Name; // Confirmed we don't get this return value 
    } 
    // It has to fall through then if no return is done! 
    } 
    throw base.GetConvertToException(value, destinationType); // BOOM! 
    // value is my custom ICommand and destinationType is System.String 
} 

Итак, тогда возникает вопрос, так как все это происходит внутри .NET, я делаю что-то неправильно, и если да, то что? Или это ошибка .NET, и если да, есть ли работа?

Спасибо за любую помощь.

ответ

1

Фантастический описание ICommand находится в этом blog entry by SkySigal, хотя мне понадобилось Google's Cache из-за проблем с настройкой блога в то время. К сожалению, конец статьи, в которой эта проблема устранена, несколько неоднозначна в ее формулировке о том, должна ли ICommand быть статической или не- статический

Оказалось, однако, было an article on dotnet mania talking about how copying a hyperlink with a custom command will crash приложение.

Кажется, эта ошибка была в .NET с 2007 года, по крайней мере, и что проблема заключается в том, что код явно проверяет «известные команды», как показал анализ рефлектора выше.

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

<Hyperlink Command="{x:Static myns:MyCommands.CustomCommand1}" .../> 

становится

<Hyperlink myns:HyperlinkHelper.Command="{x:Static myns:MyCommands.CustomCommand1}" .../> 

с бэк-кодом внутри класса HyperlinkHelper в myns имен сессии как свойство с именем Command. Это хитроумный обман, и он должен быть постыдно ненужным.

Шляпы на Eric Burke для расчета этого один из.

+0

Для этого в C#, а не XAML, это делается так: hyperlink.SetValue (HyperlinkHelper.CommandProperty, CustomCommand1); –

+0

Требуемое чтение. http://blog.hackedbrain.com/articles/UnderstandingDependencyObjectAndDependencyProperty.aspx –

+0

И это. Прикрепленные свойства - http://msdn.microsoft.com/en-us/library/ms749011.aspx –

1

Интуитивно это кажется неправильным; копирование гиперссылки должно копировать текст, независимо от того, что делает команда. Тем не менее, вы можете решить эту проблему, внедряя свой собственный TypeConverter для своего командного класса (How to Implement a Type Converter). Имейте делегировать CommandConverter, за исключением CanConvertTo: вернуть false из этого метода, чтобы сообщить фреймворку, что ваша команда не может быть преобразована в строку (или делегировать CanConvertTo в CommandConverter, а затем вернуть репрезентативную строку из ConvertTo.

+0

Спасибо, и двойное спасибо за ссылку, я должен буду дать, что выстрел. Согласился, что это кажется неправильным; если вы выполните поиск Google для сообщения об ошибке, вы увидите, что это происходит повсюду для многих людей - никто, кто, кажется, не получает разумного ответа (это, безусловно, самый информативный ответ, который я видел по теме). Я предполагаю, что он пытается помещать представление не только текста, но и ICommand в буфер обмена, если он будет вставлен в другое место, которое может справиться с ним. Тем не менее, я не знаю, почему он пытается преобразовать его в текст по умолчанию. Еще раз спасибо. –

+0

Ran в проблемы. CommandConverter запечатан, а TypeConverter при переопределении не запускает никаких методов. Мне удалось получить еще одно решение, я добавлю это через минуту - но спасибо, ваши идеи привели меня к гораздо ближе. –

0

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

Проблема возникла при попытке привязать команду к кнопке в datagrid Xceed, которая itemSource была привязана к коллекции, открытой в моей модели viewModel.

ВЗГЛЯД:

<UserControl x:Class="UnIfied.Module.UI.Client.Screens.Alerts.AlertsView" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:DataGrid="clr-namespace:Xceed.Wpf.DataGrid;assembly=Xceed.Wpf.DataGrid" xmlns:xcdg="clr-namespace:Xceed.Wpf.DataGrid.Views;assembly=Xceed.Wpf.DataGrid" xmlns:ThemePack="clr-namespace:Xceed.Wpf.DataGrid.ThemePack;assembly=Xceed.Wpf.DataGrid.ThemePack.1"> 
<Grid> 
    <DataGrid:DataGridControl Grid.Column="0" 
           Name="alertsBlotter" 
           ItemsSource="{Binding AlertsSource}" 
           SelectionMode="Single" 
           NavigationBehavior="RowOnly" 
           ItemScrollingBehavior="Immediate" ReadOnly="True" 
           AutoCreateColumns="false"> 
     <DataGrid:DataGridControl.Columns> 
      <DataGrid:UnboundColumn FieldName="Acquit" Title="Acquit Alert" ReadOnly="True" ShowInColumnChooser="False"> 
       <DataGrid:UnboundColumn.CellContentTemplate> 
        <DataTemplate> 
         <Button 
          DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType={x:Type DataGrid:DataRow}}}" 
          Content="X" Command="{Binding AcquitAlertCommand}"/> 
        </DataTemplate> 
       </DataGrid:UnboundColumn.CellContentTemplate> 
      </DataGrid:UnboundColumn> 
      <DataGrid:Column FieldName="AlertId" ReadOnly="True" Title="Alert Id" IsMainColumn="True" /> 
      <DataGrid:Column FieldName="Time" ReadOnly="True" Title="Creation Time" /> 
      <DataGrid:Column FieldName="AlertStatus" ReadOnly="True" Title="Status" /> 
      <DataGrid:Column FieldName="RelatedTrade" ReadOnly="True" Title="CT Id" /> 
      <DataGrid:Column FieldName="Status" ReadOnly="True" Title="CT Status" /> 
     </DataGrid:DataGridControl.Columns> 

     <DataGrid:DataGridControl.Resources> 
      <Style x:Key="{x:Type DataGrid:ScrollTip}" TargetType="DataGrid:ScrollTip"> 
       <Setter Property="HorizontalAlignment" Value="Center" /> 
       <Setter Property="VerticalAlignment" Value="Center" /> 
      </Style> 
     </DataGrid:DataGridControl.Resources> 
     <DataGrid:DataGridControl.View> 
      <xcdg:TableView> 
       <xcdg:TableView.Theme> 
        <ThemePack:WMP11Theme /> 
       </xcdg:TableView.Theme> 
      </xcdg:TableView> 
     </DataGrid:DataGridControl.View> 
    </DataGrid:DataGridControl> 
</Grid> 

модель представление

class AlertsViewModel : Presenter<IAlerts> 
{ 
    private readonly IAlertsService alertsService; 
    public AlertsViewModel(IAlerts view, IAlertsService aService) 
     : base(view) 
    { 
     alertsService = aService; 
     view.SetDataContext(this); 
    } 

    public ObservableCollection<AlertAdapter> AlertsSource 
    { 
     get { return alertsService.AlertsSource; } 
    } 
} 

адаптер (который затем будет представлен строкой в ​​сетке). Команда Relay является базовой реализацией ICommand.

public class AlertAdapter : BindableObject 
{ 
    private readonly RelayCommand acquitAlert; 

    public AlertAdapter() 
    { 
     AlertStatus = AlertStatus.Raised; 
     acquitAlert = new RelayCommand(ExecuteAqcuiteAlert); 
    } 

    public RelayCommand AcquitAlertCommand 
    { 
     get { return acquitAlert; } 
    } 

    private void ExecuteAqcuiteAlert(object obj) 
    { 
     AlertStatus = AlertStatus.Cleared; 
    } 

    private static readonly PropertyChangedEventArgs AlertStatusPropertyChanged = new PropertyChangedEventArgs("AlertStatus"); 
    private AlertStatus alertStatus; 
    /// <summary> 
    /// Gets or sets the AlertStatus 
    /// </summary> 
    public AlertStatus AlertStatus 
    { 
     get { return alertStatus; } 
     set 
     { 
      if (AlertStatus != value) 
      { 
       alertStatus = value; 
       RaisePropertyChanged(AlertStatusPropertyChanged); 
      } 
     } 
    } 

    private static readonly PropertyChangedEventArgs AlertIdPropertyChanged = new PropertyChangedEventArgs("AlertId"); 
    private Guid alertId; 
    /// <summary> 
    /// Gets or sets the AlertId 
    /// </summary> 
    public Guid AlertId 
    { 
     get { return alertId; } 
     set 
     { 
      if (AlertId != value) 
      { 
       alertId = value; 
       RaisePropertyChanged(AlertIdPropertyChanged); 
      } 
     } 
    } 


    private static readonly PropertyChangedEventArgs StatusPropertyChanged = new PropertyChangedEventArgs("Status"); 
    private ComponentTradeStatus status; 
    /// <summary> 
    /// Gets or sets the CtStatus 
    /// </summary> 
    public ComponentTradeStatus Status 
    { 
     get { return status; } 
     set 
     { 
      if (Status != value) 
      { 
       status = value; 
       RaisePropertyChanged(StatusPropertyChanged); 
      } 
     } 
    } 


    private static readonly PropertyChangedEventArgs RelatedTradePropertyChanged = new PropertyChangedEventArgs("RelatedTrade"); 
    private Guid relatedTrade; 
    /// <summary> 
    /// Gets or sets the RelatedTrade 
    /// </summary> 
    public Guid RelatedTrade 
    { 
     get { return relatedTrade; } 
     set 
     { 
      if (RelatedTrade != value) 
      { 
       relatedTrade = value; 
       RaisePropertyChanged(RelatedTradePropertyChanged); 
      } 
     } 
    } 


    private static readonly PropertyChangedEventArgs TimePropertyChanged = new PropertyChangedEventArgs("Time"); 
    private DateTime time; 
    /// <summary> 
    /// Gets or sets the Time 
    /// </summary> 
    public DateTime Time 
    { 
     get { return time; } 
     set 
     { 
      if (Time != value) 
      { 
       time = value; 
       RaisePropertyChanged(TimePropertyChanged); 
      } 
     } 
    } 
} 

А вот исключение генерируется как только мое приложение пыталось создать адаптер и добавить его в коллекцию

System.NotSupportedException был необработанным сообщения = "„CommandConverter“не в состоянии преобразовать ' UnIfied.Module.UI.Client.Adapters.RelayCommand 'to' System.String '. " Source = "System" StackTrace: в System.ComponentModel.TypeConverter.GetConvertToException (значение объекта, тип DestinationType) в System.Windows.Input.CommandConverter.ConvertTo (ITypeDescriptorContext контексте, CultureInfo культуры, ценности объекта, тип DestinationType) на System.ComponentModel.TypeConverter.ConvertTo (значение объекта, тип DestinationType) в System.Windows.Controls.ContentPresenter.DefaultTemplate.DoDefaultExpansion (TextBlock TextBlock, содержание объекта, ContentPresenter контейнер) (Т.Д.)

проблема была вызвана тем, что мой datagrid был настроен на AutoCreateColumns (то есть на основе свойств адаптера). Просто переключил это свойство на ложное, а потом все пошло прямо.

Надеюсь, это поможет вам, ребята!

--Bruno

0

Самый простой способ решить эту проблему, чтобы команда связывания с динамическим ресурсом, так что синтаксический анализатор не решить при попытке преобразовать команду в строку.

<Hyperlink Command="{DynamicResource NavigationCommand}">Navigate</Hyperlink> 

Проверить детали этого решения здесь http://ciintelligence.blogspot.com/2011/11/wpf-copying-hyperlink-with-command.html

1

Также ударилась головой на этом довольно долгое время. Я бы добавил это в качестве комментария к ответу Уолта Стоунбернера, но, похоже, сейчас ему нужно больше очков, чтобы сделать это в настоящее время.

В любом случае. Поскольку ссылка на оригинальное решение, похоже, была сломана, я сделал еще несколько поисковых запросов. Я в настоящее время работаю .Net Framework версии 4, и это все еще кажется очень неразрешенным (!)

Существует проблема с ошибкой, отправленная в Microsoft, и вместе с тем это временное обходное решение, я думаю, что это аналогичное решение, как описанный Уолтом Стоунбернером. Вы просто используете другое созданное свойство зависимостей вместо сложного свойства «Command» на copy-paste, а остальные обрабатываются вспомогательным классом. Вы можете скачать zip отсюда, нажмите «Показать ссылку», чтобы получить к ней доступ. Спасибо Боб Bao для его размещения:

http://connect.microsoft.com/VisualStudio/feedback/details/637269/copying-a-command-bound-hyperlink-in-a-flowdocument-throws-an-exception

Microsoft, кажется, выпустили решение сейчас, «HyperlinkHelper» это называется. По какой-то неудобной причине они, похоже, решили распространять ее в какой-либо команде dll (?). Вы можете найти документацию для нее по ссылке ниже. Если вам посчастливилось использовать сервер Team Foundation, вы можете даже использовать этот класс напрямую. В противном случае я бы рекомендовал повторно использовать указанное выше решение.

http://technet.microsoft.com/en-us/subscriptions/microsoft.teamfoundation.controls.wpf.hyperlinkhelper

+0

Спасибо Хенрик, вы отлично поработали с поиском актуальной информации. –