В моей Datagrid есть столбец, в котором есть индикаторы прогресса, каждый из которых должен меняться независимо от других, когда я обращаюсь к определенной строке в данной Datagrid.
При попытке обратиться к определенному прогрессбару в определенной строке ничего не происходит, я так и сделал, в надежде, что они начнут меняться хоть все сразу.
private void ColorRow(System.Windows.Controls.DataGrid dg)
{
DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(cmdvm.NumberOfFrame);
if (row != null )
{
if (cmdvm.NumberOfFrame > 0)
{
row.Background = brush;
cmdvm.PercentageOfFrame = registers[61];
ProgressBar progressbar = (ProgressBar)dg.FindName("progressbar");
progressbar.Value = registers[61];
}
}
}
КСАМЛ:
<DataGrid x:Name = "Datagrid_CmdLines" SelectionUnit = "Cell" HeadersVisibility = "None" ItemsSource = "{Binding CMD_Array}" Margin = "52,308,0,0" AutoGenerateColumns = "False" Width = "635" Height = "393" HorizontalAlignment = "Left" VerticalAlignment = "Top" Background = "#FFEEEEEE" Grid.Row = "1">
<DataGrid.RowStyle>
<Style TargetType = "DataGridRow">
<Setter Property = "IsHitTestVisible" Value = "False"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width = "150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ProgressBar x:Name = "progressbar" Value = "{Binding PercentageOfFrame, RelativeSource = {RelativeSource AncestorType = {x:Type DataGridRow}}}" Minimum = "0" Maximum = "100" Height = "20"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Также моя ViewModel:
public class CMD_VM : INotifyPropertyChanged
{
private string[]? _cmd_array;
private int _numberOfFrame;
private int _percentage;
public string[] CMD_Array
{
get
{
return _cmd_array!;
}
set
{
_cmd_array = value;
NotifyPropertyChanged("CMD_Array");
}
}
public int NumberOfFrame
{
get
{
return _numberOfFrame;
}
set
{
_numberOfFrame = value;
NotifyPropertyChanged("NumberOfFrame");
}
}
public int PercentageOfFrame
{
get
{
return _percentage;
}
set
{
_percentage = value;
NotifyPropertyChanged("PercentageOfFrame");
}
}
public event PropertyChangedEventHandler? PropertyChanged;
private void NotifyPropertyChanged(String propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Как я могу обращаться к каждому индикатору выполнения отдельно?





Если вам нужен прогресс для каждой строки, вам следует создать модель представления для каждой строки со свойством прогресса, то есть что-то вроде:
public class MyRowViewModel : INotifyPropertyChanged{
public MyRowViewModel(Progress<int> progress){
progress.ProgressChanged += (o, e) => PercentageOfFrame = e;
}
public int PercentageOfFrame {...}
}
...
public class CMD_VM{
public ObservableCollection<MyRowViewModel> CMD_Array {get;} = new ();
}
...
<ProgressBar Value = "{Binding PercentageOfFrame}" Minimum = "0" Maximum = "100" Height = "20"/>
Я не уверен, как вы планируете сообщать о прогрессе, но вам, вероятно, следует использовать Progress<T>, чтобы обеспечить правильное сообщение о прогрессе в потоке пользовательского интерфейса.
Похоже, вы неправильно поняли, как работает WPF ItemsControl. Какие бы элементы вы ни привязали к свойству ItemsSource, они будут DataContext из DataGridRow и DataGridTemplateColumn.CellTemplate.
Например, если вы привязываете свой DataGrid к коллекции элементов string, тогда каждый DataGridRow будет иметь фактическое значение string как DataContext этой строки. Этот string предмет также является DataContext из DataTemplate из DataGridTemplateColumn.CellTemplate.
Вот причина, по которой привязка вашего ProgressBar не работает: string не имеет PercentageOfFrame свойства. Это свойство определено в DataContextDataGrid (класс модели представления CMD_VM).
Фиксированная привязка должна быть:
<ProgressBar x:Name = "progressbar"
Value = "{Binding PercentageOfFrame, RelativeSource = {RelativeSource AncestorType=DataGrid.DataContext}}" />
Затем обновите значение прогресса следующим образом:
private void ColorRow(System.Windows.Controls.DataGrid dg)
{
// You must check the index BEFORE you use it and not afterwards!
if (cmdvm.NumberOfFrame <= 0)
{
return;
}
// This line of code has potential to fail:
// DataGrid uses row virtualization. If the requested row container is not visible,
// the ItemContainerGenerator will return NULL!
// Instead of handling row containers you should introduce row item models
// that bind to the row container. This way this code will always work
DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(cmdvm.NumberOfFrame);
if (row != null)
{
row.Background = brush;
// Because of the data binding set in the XAML updating the source
// property will update the ProgressBar
cmdvm.PercentageOfFrame = registers[61];
}
}
Однако вы, скорее всего, не захотите привязывать ProgressBar каждой строки к одному и тому же свойству PercentageOfFrame. Обновление свойства приведет к изменению PropregssBar во всех строках одновременно. Также нет необходимости явно устанавливать ProgressBar.Value из вашего кода, если вы присвоили Binding этому свойству. Явная установка этого свойства приведет к удалению Binding и, следовательно, к поломке вашего кода. Вместо этого обновите исходное свойство PercentageOfFrame. Привязка отправит новое значение в ProgressBar.
Как предлагают другие, вы должны ввести модель данных. Вот как следует использовать любой ItemsControl в WPF.
Ровитем.cs
public class RowItem : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
private int _percentage;
private Color _color;
public string Value { get; private set; }
public Color Color
{
get => _color;
set
{
_color = value;
NotifyPropertyChanged();
}
}
public int PercentageOfFrame
{
get => _percentage;
set
{
_percentage = value;
NotifyPropertyChanged();
}
}
public RowItem(string value)
=> Value = value;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
CMD_VM.cs
public class CMD_VM : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public ObservableCollection<RowItem> RowItems { get; }
// If this is a per-row property then move it to the RowItem class
private int _numberOfFrame;
public int NumberOfFrame
{
get => _numberOfFrame;
set
{
_numberOfFrame = value;
NotifyPropertyChanged();
}
}
public CMD_VM()
{
// Initialize the read-only collection.
// Do not replace the collection instance (bad perfromance).
// Instead add/remove items to/from the collection.
this.RowItems = GetRowItems();
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
=> this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
Установите прогресс строки:
private void ColorRow()
{
// Are you sure that you want to check if the index NumberOfFrame
// is > 0 (and not >= 0)?
// In your original code you were checking whether the index is > 0.
// '0' is a valid index. To invalidate cmdvm.NumberOfFrame, this
// property should be set to `-1` and not '0'.
// Then the condition would change to: cmdvm.NumberOfFrame >= 0
if (cmdvm.NumberOfFrame > 0
&& cmdvm.NumberOfFrame < cmdvm.RowItems.Count)
{
RowItem row = cmdvm.RowItems[cmdvm.NumberOfFrame];
// Assign Color objects and not Brush objects
row.Color = Colors.Red;
row.PercentageOfFrame = registers[61];
}
}
Правильно привяжите DataGrid к новому источнику данных, а столбец CellTemplate к модели данных:
<DataGrid ItemsSource = "{Binding RowItems}">
<DataGrid.RowStyle>
<Style TargetType = "DataGridRow">
<!-- Bind the row's Background to the RowItem (the DataContext of the DataGridRow) -->
<Setter Property = "Background">
<Setter.Value>
<SolidColorBrush Color = "{Binding Color}" />
</Setter.Value>
</Setter>
<Setter Property = "IsHitTestVisible"
Value = "False" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTemplateColumn Width = "150">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType = "{x:Type RowItem}">
<ProgressBar Value = "{Binding PercentageOfFrame}"
Minimum = "0"
Maximum = "100"
Height = "20" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Я думал, что вы где-то инициализировали коллекцию исходников. Это всего лишь метод-заполнитель для представления инициализации. Вы пропустили эту часть, и я тоже. Я просто почувствовал, что должен показать, что вы инициализируете доступное только для чтения свойство типа ObservableCollection, противоположное вашему свойству массива, с помощью средства установки свойства и уведомления об изменении свойства. Я хотел подчеркнуть, что вы инициализируете свойство из конструктора, а затем никогда не меняете экземпляр коллекции. Это важная часть при привязке к коллекциям, позволяющая избежать замены экземпляра и вместо этого добавлять/удалять элементы.
В любом случае, спасибо за добрые слова и высокую оценку. Я рад, что смог помочь.
Я очень благодарен вам за ваш подробный ответ, и вы помогли мне внести больше ясности. Просто не совсем понял, о какой функции GetRowItems() [в public CMD_VM] вы говорите, не могли бы подсказать? Спасибо.