#wpf #wpfdatagrid
#wpf #wpfdatagrid
Вопрос:
У меня есть список классов ‘Rule’, который является источником DataGrid. В этом примере у меня есть один из столбцов, который является DataGridTemplateColumn, который привязан к свойству зависимости ‘Verified’.
Проблема, с которой я сталкиваюсь, заключается в том, что у меня есть VerifyColorConverter, куда я хочу передать ВЕСЬ объект ‘Rule’ выбранной строки, чтобы я мог изучить экземпляр правила и вернуть соответствующую кисть. Я делаю это при настройке фона границы (см. Код ниже — Background=»{Binding Converter={StaticResource convVerify}}»)
<DataGridTemplateColumn Header="Verified" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding Converter={StaticResource convVerify}}"
CornerRadius="4" Height="17" Margin="2,0,2,0" VerticalAlignment="Center" >
<Grid>
<TextBlock Foreground="Yellow" Text="{Binding Path=Verified, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center"
FontSize="11" FontWeight="Bold" />
</Grid>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Все это хорошо работает, когда я устанавливаю источник в DataGrid, но когда изменяется базовый объект ‘Rule’, конвертер не вызывается, поэтому кисть остается прежней. Как я могу принудительно обновить это при изменении некоторых свойств экземпляра ‘Rule’?
Любая помощь приветствуется!
Конвертер выглядит примерно так:
[ValueConversion(typeof(CRule), typeof(SolidColorBrush))]
public class VerifyColorConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
CRule rule = value as CRule;
Color clr = Colors.Red;
int count = 0;
int verified = 0;
if (rule != null)
{
count = rule.TotalCount;
verified = rule.NoOfVerified;
}
if (count == 0) clr = Colors.Transparent;
else if (verified == 0) clr = (Color)ColorConverter.ConvertFromString("#FFD12626");
else if (verified < count) clr = (Color)ColorConverter.ConvertFromString("#FF905132");
else clr = (Color)ColorConverter.ConvertFromString("#FF568D3F");
SolidColorBrush brush = new SolidColorBrush(clr);
return brush;
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Редактировать
Это часть класса правил:
/// <summary>
/// Compliance Restriction (Rule)
/// </summary>
public class Rule : BindElement
{
public CMode Mode { get; private set; }
public int RuleID { get; private set; }
public string RuleDescription { get; private set; }
private int _NoOfVerified = 0;
private int _TotalCount = 0;
public int NoOfVerified
{
get { return _NoOfVerified; }
set { _NoOfVerified = value; RaiseChanged("Progress"); RaiseChanged("Verified"); }
}
public int TotalCount
{
get { return _TotalCount; }
set { _TotalCount = value; RaiseChanged("Progress"); RaiseChanged("Verified"); }
}
public string Verified
{
get
{
if (TotalCount == 0) return "Nothing to verify";
return string.Format("Verified {0} out of {1}", NoOfVerified, TotalCount);
}
}
Ответ №1:
Вы могли бы попробовать использовать MultiValueConverter
вместо обычного Converter
и передать ему любые свойства правила, которые вам нужны, или вы можете вызвать CollectionChanged
событие при изменении свойства в правиле. Обычно я предпочитаю этого не делать, поскольку не знаю, как это влияет на производительность, но это вариант.
Использование мультиконвертера
public object Convert(object[] values, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
Color clr = Colors.Red;
int count = 0;
int verified = 0;
if (values.Count >= 2
amp;amp; int.TryParse(count, values[0].ToString())
amp;amp; int.TryParse(verfieid, values[1].ToString()))
{
if (count == 0) clr = Colors.Transparent;
else if (verified == 0) clr = (Color)ColorConverter.ConvertFromString("#FFD12626");
else if (verified < count) clr = (Color)ColorConverter.ConvertFromString("#FF905132");
else clr = (Color)ColorConverter.ConvertFromString("#FF568D3F");
}
SolidColorBrush brush = new SolidColorBrush(clr);
return brush;
}
XAML для мультиконвертера:
<Border.Background>
<MultiBinding Converter="{StaticResource convVerify}">
<Binding Value="{Binding TotalCount}" />
<Binding Value="{Binding NoOfVerified}" />
</MultiBinding>
</Border.Background>
Использование событий изменения свойств
RulesCollection.CollectionChanged = RulesCollection_Changed;
void RulesCollection_Changed(object sender, CollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach(Rule rule in e.NewItems) // May need a cast here
rule.PropertyChanged = Rule_PropertyChanged;
if (e.OldItems != null)
foreach(Rule rule in e.OldItems) // May need a cast here
rule.PropertyChanged -= Rule_PropertyChanged;
}
void Rule_PropertyChanged()
{
RaisePropertyChanged("RulesCollection");
}
Комментарии:
1. Я не могу этого сделать, поскольку он является частью шаблона DataGridTemplateColumn, а не к объекту правила, который у меня есть. Я передаю сетке список объектов правил и хочу передать правило преобразователю, а не определять путь, как я делаю в других столбцах (или в том текстовом блоке, который я показываю в примере кода).
2. Я думаю, я понимаю, что вы сейчас говорите — я неправильно понял вопрос. Из вашей ViewModel почему бы не добавить
PropertyChange
событие к каждому,Rule
которое вызываетOnPropertyChanged
событие для всего правила при изменении любого из параметров?3. Пока спасибо за вашу помощь, Рэйчел — я обновил код, чтобы показать часть класса правил. Я понимаю, что вы имеете в виду своим комментарием, но как бы я поступил, если бы я поместил список<Правило> в источник сетки? Как мне «пнуть» это конкретное правило?
4. Не понимал, что вы привязываетесь к
List
. Попробуйте заменитьList<Rule>
наObservableCollection<Rule>
. По умолчанию List не уведомляет пользовательский интерфейс при изменении его свойств, в то время какObservableCollection
это делает.5. Извините — моя ошибка — я привязываюсь к наблюдаемой коллекции — я сказал это неправильно… (это моя строка: gridRules. ItemsSource = _ocRules;)
Ответ №2:
ХОРОШО — я нашел способ обойти это — то, что я сделал, это предоставить объект как свойство, а затем вызвать OnPropertyChanged для этого свойства всякий раз, когда что-либо меняется. Это правильно распознается объектом привязки и передается преобразователю при каждом изменении свойства!!
[Serializable()]
public class BindElement : INotifyPropertyChanged
{
#region INotifyPropertyChanged Members
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null)
PropertyChanged(this, e);
}
public void RaiseChanged(string s)
{
OnPropertyChanged(new PropertyChangedEventArgs(s));
}
#endregion
}
public class BindElement2 : BindElement
{
public void RaiseChanged(string s)
{
base.RaiseChanged(s);
NudgeMyself();
}
public void NudgeMyself()
{
base.RaiseChanged("Myself");
}
}
/// <summary>
/// Compliance Restriction (Rule)
/// </summary>
public class CRule : BindElement2
{
public CMode Mode { get; private set; }
public int RuleID { get; private set; }
public string RuleDescription { get; private set; }
private int _NoOfVerified = 0;
private int _TotalCount = 0;
public int NoOfVerified
{
get { return _NoOfVerified; }
set { _NoOfVerified = value; RaiseChanged("Progress"); RaiseChanged("Verified"); }
}
public int TotalCount
{
get { return _TotalCount; }
set { _TotalCount = value; RaiseChanged("Progress"); RaiseChanged("Verified"); }
}
public string Verified
{
get
{
if (TotalCount == 0) return "Nothing to verify";
return string.Format("Verified {0} out of {1}", NoOfVerified, TotalCount);
}
}
public CRule Myself
{
get { return this; }
}
и другие классы могут быть производными от BindElement2 и делать то же самое: (создайте свойство самостоятельно, которое предоставляет сам экземпляр)
public class CTradeRule : BindElement2
{
public CRule Rule { get; set; }
public bool IsLocked { get; set; } // if true this should prevent a Reason from being given
public bool IsVerified { get { return Reason.Length > 0; } }
private string _Reason = ""; // ** no reason **
public string Reason
{
get { return _Reason; }
set { _Reason = value; RaiseChanged("Reason"); }
}
public int Progress
{
get { return (IsVerified ? 1 : 0); }
}
public override string ToString()
{
return string.Format("Rule: {0}, Reason: {1}", Rule.RuleID, _Reason);
}
public CTradeRule Myself
{
get { return this; }
}
}
Теперь в xaml я могу сделать это: (обратите внимание на путь привязки = Себя), который затем гарантирует, что весь объект отправляется в конвертер ПРИ каждом изменении любого свойства!!
<DataGridTemplateColumn Header="Verified" Width="150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Background="{Binding Path=Myself, Converter={StaticResource convVerify}}"
CornerRadius="4" Height="17" Margin="2,0,2,0" VerticalAlignment="Center" >
<Grid>
<TextBlock Foreground="Yellow" Text="{Binding Path=Verified, Mode=OneWay}" TextAlignment="Center" VerticalAlignment="Center"
FontSize="11" FontWeight="Bold" />
</Grid>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>