Использование ресурсов в качестве результатов преобразования в конвертере привязки

#wpf #staticresource #valueconverter

#wpf #staticresource #valueconverter

Вопрос:

Когда я пытаюсь привязать valueconverter из определенного состояния перечисления к brush, я получаю ошибку в моем XAML designer:

ресурс ‘OKStatus’ не найден.

Приложение отлично работает во время выполнения, но я не могу увидеть свой графический интерфейс в конструкторе. Мои ресурсы определены в файле color.xaml, который считывается во время выполнения. Весь код находится в одном пространстве имен

Мой XAML:

xmlns:config=»clr-namespace:App.MyNamespace»

 <UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="c:SkinsColors.xaml" />
            <ResourceDictionary Source="c:SkinsCommon.xaml" />                
        </ResourceDictionary.MergedDictionaries>
        <config:StatusConverter x:Key="StateConverter" />
        <config:BoolConverter x:Key="BoolConverter" />
        <config:BooleanConverter x:Key="BooleanConverter" />
    </ResourceDictionary>
</UserControl.Resources>
  

и

Статус

Мой конвертер:

 [ValueConversion(typeof(bool), typeof(Brush))]
public class BoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        bool state = (bool)value;

        FrameworkElement FrameElem = new FrameworkElement();

        if (state == true)
            return (FrameElem.FindResource("OKStatus") as Brush);
        else
            return (FrameElem.FindResource("ErrorStatus") as Brush);
    }

    public object ConvertBack(object value, Type targetType,
        object parameter, CultureInfo culture)
    {
        return null;
    }
}
  

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

Ответ №1:

Да, это возможно, и ваше предположение верно. Поиск ресурсов начинается с логического дерева, и создание нового FrameworkElement() не удовлетворяет этому. Он полностью отключен.

Что вы можете сделать (и что вам, возможно, придется сделать, если предложение N8 не сработает), так это передать вашему конвертеру ссылку на UserControl как на FrameworkElement вызываемый FindResource() .

Причина, по которой предложение N8, вероятно, не сработает, заключается в том, что Application.Current.FindResource() , вероятно, начинается с ресурсов уровня приложения, а затем переходит к системным ресурсам, но ресурсы, которые вам нужны, находятся в UserControl ресурсах. Если бы они были размещены в ресурсах App.xaml, это бы сработало. Тем не менее, я думаю, что Application.Current может быть null во время разработки.

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

 public MyUserControl(){
    var boolconv = new BoolConverter(); 
    boolconv.FrameworkElement = this;
    this.Resources.Add( "BoolConverter", boolconv );
    InitializeComponent();
}
  

Я почти уверен, что это происходит до InitializeComponent() , а не после.

Сделать это в XAML было бы сложнее, так как вам, вероятно, придется добавить DependencyProperty в свой конвертер, чтобы вы могли привязать UserControl к нему. Я думаю, что это было бы уже чересчур.

Другой способ — поместить TrueBrush и FalseBrush свойства в ваш конвертер и назначить их в XAML, что я обычно и делаю, чтобы мои конвертеры были расплывчатыми и общедоступными. (ПРИМЕЧАНИЕ: имена немного отличаются.)

 <config:BoolToBrushConverter x:Key="Bool2Brush"
                      TrueBrush="{StaticResource OKStatusBrush}"
                      FalseBrush="{StaticResource ErrorStatusBrush}" />
  

Комментарии:

1. Спасибо, Джоэл, ваше решение звучит намного чище. Я довольно новичок в мире WPF.

2. Это (с использованием свойств), безусловно, более приятное решение.

Ответ №2:

Я думаю, проблема в том, что вы пытаетесь найти ресурс из элемента framework, которого нет в визуальном дереве. Не могли бы вы попробовать следующее вместо этого?

 Application.Current.FindResource("OKStatus") as Brush;
  

Комментарии:

1. дайте мне знать, если я ошибаюсь.

2. Это определенно сработало бы, если бы ресурсы находились в словаре уровня приложения или системных ресурсах.

Ответ №3:

Как я узнал из TechNet Wiki, необходимо использовать MultiValue Converter и MultiValueBinding, чтобы получить правильный зарегистрированный конвертер и правильный FrameworkElement с помощью UserControl.

Пример XAML:

 <TextBlock x:Name="tb1" Margin="20">
 <TextBlock.Text>
  <MultiBinding Converter="{StaticResource MyConverter}">
   <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}"/>
   <Binding Path="MyValue"/>
  </MultiBinding>
</TextBlock.Text>
</TextBlock>
  

Тогда объявление конвертера может выглядеть :

 public class MyConverter : IMultiValueConverter
{
     FrameworkElement myControl;
     object theValue;

     public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
        myControl = values[0] as FrameworkElement;
        theValue = values[1];

         return myControl.FindResource(">>resource u need<<"); 
      }

       public object[] ConvertBack(object value, System.Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
  {
  .....
  }
}
  

Подробное объяснение таково:
https://social.technet.microsoft.com/wiki/contents/articles/12423.wpfhowto-pass-and-use-a-control-in-it-s-own-valueconverter-for-convertconvertback.aspx

Ответ №4:

Я также столкнулся с этой проблемой. Я думаю, что вызов Application.Current — лучший способ получить ресурсы из IValueConverter , поскольку они не определены на уровне для каждого окна, страницы или элемента управления. Для этого потребуются ресурсы, по крайней мере, уровня приложения, как указано выше.

Однако, поскольку Application.Current ссылке в конструкторе присвоено значение null, этот метод всегда приведет к сбою конструктора. То, что вы, похоже, сделали, это предоставили что-то для отображения дизайнеру, в то время как вы предоставили вашему запущенному приложению доступ к ресурсам в конвертере.

Для всех вас, кто сталкивается с этой проблемой, вам не нужно внедрять Kludge, реализованный lewi; это только в том случае, если вы хотите, чтобы загружалась designer surface. Это не влияет на ваше приложение во время выполнения, поскольку Application.Current вызову есть что делать.

Ответ №5:

На самом деле, что я в итоге сделал (на данный момент), так это изменил FindResource на TryFindResource и поместил инструкции в блок try / catch. Похоже, пока это работает.

 try
{
    if (state == true)
       return (FrameElem.TryFindResource("OKStatus") as Brush);
    else
       return (FrameElem.TryFindResource("ErrorStatus") as Brush);
}

catch (ResourceReferenceKeyNotFoundException)
{
   return new SolidColorBrush(Colors.LightGray);
}
  

Комментарии:

1. Я лично выбрал решение Джоэла выше, поскольку оно кажется гораздо менее трудоемким с точки зрения вычислений. Использование TryFindResource сказалось бы на моем приложении, где мне пришлось изменять цвета элементов в огромной сетке данных.