转换器的竞态条件
本文关键字:条件 转换器 | 更新日期: 2023-09-27 18:18:25
我有一个wpf表单,它有相当多的动态创建的控件,例如ComboBox
es。
在每个ComboBox
上,我有几个转换器来处理业务逻辑。其中一个转换器自动填充N/A
并根据表单上的其他输入禁用它。然而,这应该只允许被自动填充(用户不应该能够选择这个)。
要实现这一点,我在ComboBox的ItemsSource
上有一个转换器,如果启用了ComboBox
,它将过滤掉N/A
选项,否则我将包含它,以便可以根据需要填充它。我还有一个用于SelectedItem的转换器,用于自动填充N/A
答案。
然而,似乎存在一个竞争条件,因为转换器不以相同的顺序一致地触发,导致空白答案(在ItemsSource转换器运行以添加答案之前,自动填充selecteditem的转换器正在运行)。
是否有办法确保转换器以相同的方式执行?我在代码后面创建绑定/转换器(因为控件是动态创建的),如果这有区别的话。
编辑:添加我的代码每个请求(然而我的问题是一个更一般的问题)。这里是我的代码后面,我定义绑定/转换器(我有一个模板的每个控件在我的xaml,我克隆它在这里):
//Filter out AnswerOptions (N/A, Yes, No) based on other questions
MultiBinding itemsSourceBinding = new MultiBinding();
itemsSourceBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.ImageAdequacyProperty.SingleAnswer") { Source = this });
itemsSourceBinding.Bindings.Add(new Binding(".") { Source = containerBase });
itemsSourceBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.MissingTOptionProperty.IsSelected") { Source = this });
itemsSourceBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.MissingLOptionProperty.IsSelected") { Source = this });
itemsSourceBinding.Bindings.Add(new Binding("AnswerOptions") { Source = containerBase });
itemsSourceBinding.Converter = new EndplateAnswerFilterConverter();
BindingOperations.SetBinding(comboBox, ComboBox.ItemsSourceProperty, itemsSourceBinding);
//Binding to auto-populate Answers based on Image Adequacy Answer
MultiBinding singleAnswerBinding = new MultiBinding();
singleAnswerBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.ImageAdequacyProperty.SingleAnswer") { Source = this });
singleAnswerBinding.Bindings.Add(new Binding(".") { Source = containerBase });
singleAnswerBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.MissingTcOptionProperty.IsSelected") { Source = this });
singleAnswerBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.MissingLOptionProperty.IsSelected") { Source = this });
singleAnswerBinding.Converter = new EndplateNotReadabletoAnswerConverter();
BindingOperations.SetBinding(containerBase, ContainerBase.SingleAnswerProperty, singleAnswerBinding);
以下是我的转换器:
ItemsSource转换器过滤出答案:
public class EndplateAnswerFilterConverter : IMultiValueConverter
{
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] is Answer && values[1] is ContainerBase && values[2] is bool && values[3] is bool && values[4] is ObservableCollection<Answer> /*targetType == typeof(Visibility)*/)
{
ObservableCollection<Answer> itemsSource = new ObservableCollection<Answer>();
var answerOptions = (ObservableCollection<Answer>)values[4];
var imageAdequacyAnswer = (Answer)values[0];
var containerBase = (ContainerBase)values[1];
var missingTOptionSelected = (bool)values[2];
var missingLOptionSelected = (bool)values[3];
//Loop through AnswerOptions
foreach (var ans in answerOptions)
{
//Add N/A option if NR/Missing Images
if (ans.Value == ((int)YesNoAnswers.NotApplicable).ToString() &&
(imageAdequacyAnswer.Value == ((int)ImageAdequacyAnswers.NotReadable).ToString() ||
(missingTOptionSelected && containerBase.Name.Contains("_T")) ||
(missingLOptionSelected && containerBase.Name.Contains("_L"))))
{
itemsSource.Add(ans);
}
//Add Yes/No otherwise
else if (ans.Value != ((int)YesNoAnswers.NotApplicable).ToString() &&
!(imageAdequacyAnswer.Value == ((int)ImageAdequacyAnswers.NotReadable).ToString() ||
(missingTOptionSelected && containerBase.Name.Contains("_T")) ||
(missingLOptionSelected && containerBase.Name.Contains("_L"))))
{
itemsSource.Add(ans);
}
}
return itemsSource;
}
else
{
return null;
}
}
自动填充组合框的转换器(ContainerBase是一个自定义用户控件,它将ComboBox
封装为其他属性):
public class EndplateNotReadabletoAnswerConverter : IMultiValueConverter
{
public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values[0] is Answer && values[1] is ContainerBase && values[2] is bool && values[3] is bool /*targetType == typeof(Visibility)*/)
{
var imageAdequacyAnswer = (Answer)values[0];
var containerBase = (ContainerBase)values[1];
var missingTOptionSelected = (bool)values[2];
var missingLOptionSelected = (bool)values[3];
if (imageAdequacyAnswer.Value == ((int)ImageAdequacyAnswers.NotReadable).ToString() ||
(missingTOptionSelected && containerBase.Name.Contains("_T")) ||
(missingLOptionSelected && containerBase.Name.Contains("_L")))
{
return containerBase.GetAnswerOptionByValue(((int)YesNoAnswers.NotApplicable).ToString());
}
return null;
}
else
{
return null;
}
}
我想可能会有一种情况,你得到两个不同的转换器;因此创建了两个不同的数据集和一个明显的竞争条件。
为了解决这种竞态条件可能出现的问题,我建议执行这两步;两者都可以独立完成,但建议同时完成。
- 在转换器中放入
lock
部分并锁定同步对象,以阻止任何双重访问/创建项目,最终进入连击。 - 创建一个基于单例的转换器,其中转换器负责自己的创建,并且无论哪个页面需要转换器,只有一个转换器获得完全相同的。
要让转换器创建自己,我建议创建一个泛型基类,它处理转换器的单例创建并为转换器提供服务。下面的代码来自我的博客文章《Xaml:调用绑定转换器而不用在Xaml中定义StaticResource》,这要感谢c#中的标记派生基类。
在那篇文章中,我们从未在xaml中创建静态资源转换器,而是直接在xaml中调用转换器,例如:<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:converters="clr-namespace:Omega.Operation.Converters"
...
>
<Combo ItemsSource="{Binding ComboData,
Converter={ converters:EndplateAnswerFilterConverter }
}">
要在单例中实现转换器的直接绑定和一个转换器的创建,需要实现以下基类:
/// <summary>
/// This creates a Xaml markup which can allow converters (which inheirit form this class) to be called directly
/// without specify a static resource in the xaml markup.
/// </summary>
public class CoverterBase<T> : MarkupExtension where T : class, new()
{
private static T _converter = null;
public CoverterBase() { }
/// <summary>Create and return the static implementation of the derived converter for usage in Xaml.</summary>
/// <returns>The static derived converter</returns>
public override object ProvideValue(IServiceProvider serviceProvider)
{
return _converter ?? (_converter = (T) Activator.CreateInstance(typeof (T), null));
}
}
最后,转换器本身需要指定它的基值:
namespace Omega.Operation.Converters
{
public class EndplateAnswerFilterConverter : CoverterBase<EndplateAnswerFilterConverter>,
System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
lock (syncObject)
{}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
lock (syncObject)
{}
}
}
}