Why is my ItemsControl not updating on one page? [closed] - c#

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed yesterday.
Improve this question
I have a ItemsControl object that is based on a canvas. When the program is first loaded, this items control has no children. When the navigates to the file page and opens a job, the ObservedCollection of CanvasLine objects is updated and is supposed to then draw the new CanvasLines on the ItemsControl but this is continually not working.
What is really stumping me about this is that if I copy this exact same ItemsControl to a different page, it works perfectly and adds the new lines when the job is opened. I would really appreciate any input on why this would affect different pages differently because I've been on this awhile and I am absolutely stumped.
Here is my CanvasLine class:
public class CanvasLine : INotifyPropertyChanged
{
private Point canvasStart;
private Point canvasEnd;
public Point DxfStart { get; set; }
public Point DxfEnd { get; set; }
public string Layer { get; set; }
public List<double> Extents { get; set; }
public double Scale { get; set; }
public double CanvasWidth { get; set; }
public double CanvasHeight { get; set; }
public Point CanvasStart
{
get { return canvasStart; }
set
{
canvasStart = value;
OnPropertyChanged();
}
}
public Point CanvasEnd
{
get { return canvasEnd; }
set
{
canvasEnd = value;
OnPropertyChanged();
}
}
public CanvasLine(Point dxfStart, Point dxfEnd, string layer)
{
DxfStart= dxfStart;
DxfEnd= dxfEnd;
Layer = layer;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public void GetCanvasLines(List<double> extents, double canvasWidth, double canvasHeight)
{
Extents = extents;
CanvasWidth = canvasWidth;
CanvasHeight = canvasHeight;
CanvasStart = DXFToCanvas.DXFCoordToCanvas(DxfStart, Extents, CanvasWidth, CanvasHeight);
CanvasEnd = DXFToCanvas.DXFCoordToCanvas(DxfEnd, Extents, CanvasWidth, CanvasHeight);
}
public void DrawLines(Canvas canvas)
{
Line line = new Line();
line.X1 = CanvasStart.X;
line.Y1 = CanvasStart.Y;
line.X2 = CanvasEnd.X;
line.Y2 = CanvasEnd.Y;
line.StrokeThickness = 0.5;
line.Stroke = Brushes.Black;
canvas.Children.Add(line);
}
}
}
Here is my XAML for the ItemsControl:
<Canvas Background="White"
ClipToBounds="True"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="600" Height="300">
<ItemsControl ItemsSource="{Binding CanvasLines}" x:Name="MainPageItemsControl"
Width="600" Height="300">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" Width="600" Height="300"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Line X1="{Binding CanvasStart.X}" Y1="{Binding CanvasStart.Y}"
X2="{Binding CanvasEnd.X}" Y2="{Binding CanvasEnd.Y}"
Stroke="Black" StrokeThickness="0.5"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.RenderTransform>
<MatrixTransform x:Name="transform"/>
</ItemsControl.RenderTransform>
</ItemsControl>
</Canvas>
I'm populating this CanvasLines ObservableCollection using the following code:
public TestMainPage()
{
InitializeComponent();
DataContext = this;
if (Globals.CAD_LOADED)
{
LoadCanvasDXF();
}
}
public void LoadCanvasDXF()
{
extents = LoadDXF.LoadExtents(Globals.CAD_FILE_PATH);
foreach (CanvasLine canvasLine in Globals.LINE_LIST)
{
canvasLine.GetCanvasLines(Extents, TestMainPageItemsControl.Width, TestMainPageItemsControl.Height);
CanvasLines.Add(canvasLine);
}
}

Related

How to write a DataTemplate that works with a VisualStateGroup in WinUI 3?

I've been trying to pass collections of VisualStateGroup objects to various ContentControls in WinUI 3 (1.1.5) but I keep getting a 'Value does not fall within the expected range.' error. I think I've defined an appropriate DataTemplate, but nothing seems to work. Here's a simple example Page from a new Template Studio Winui 3 project:
public sealed partial class MainPage : Page
{
public MainViewModel ViewModel { get; }
public ShellPage ShellPage { get; }
public MainPage()
{
ViewModel = App.GetService<MainViewModel>();
InitializeComponent();
ShellPage = App.GetService<ShellPage>();
var rootGrid = (ShellPage.IsLoaded) ? VisualTreeHelper.GetChild(ShellPage.NavigationViewControl, 0) as Grid : null;
if (rootGrid != null)
{
var listOfVisualStateGroups = VisualStateManager.GetVisualStateGroups(rootGrid);
if (listOfVisualStateGroups?.Count > 0) cControl.Content = listOfVisualStateGroups[0];
}
}
}
<Page
x:Class="TestCollection.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<ContentControl x:Name="cControl">
<ContentControl.ContentTemplate>
<DataTemplate x:DataType="VisualStateGroup">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</Grid>
</Page>
All this does is fetch the list of VisualStateGroups attached to the NavigationViewControl of a vanilla WinUI 3 project. The first group in the list is then displayed in the ContentControl cControl by assigning it to Content. But the assignment causes the error.
I gather there is something wrong with the DataTemplate, but I can't figure out what. A similar problem occurs if I try to create a templated ListViewItem with a VisualStateGroup object.
This is a significant refinement of a question I asked a few days ago (please see: Bound ListView won't accept List<VisualStateGroup> as ItemsSource in WinUI 3. Any idea why?, if curious). My purpose in asking is to better understand the mechanics and requirements of templating. Any help is greatly appreciated.
Update: 10/2/2022
I haven't found an explanation for the error but I have found that simply wrapping the VisualStateGroup and VisualState classes lets me use collections of the new classes as I would expect. Thing1 and Thing2 are just copies of VisualStateGroup and VisualState (both of which are sealed and can't be inherited directly).
public sealed partial class MainPage : Page
{
public MainViewModel ViewModel { get; }
public ShellPage ShellPage { get; }
public List<Thing1> Things { get; set; } = new();
public List<Thing2> OtherThings { get; set; } = new();
public MainPage()
{
ViewModel = App.GetService<MainViewModel>();
InitializeComponent();
ShellPage = App.GetService<ShellPage>();
var rootGrid = (ShellPage.IsLoaded) ? VisualTreeHelper.GetChild(ShellPage.NavigationViewControl, 0) as Grid : null;
if (rootGrid != null)
{
var listOfVisualStateGroups = VisualStateManager.GetVisualStateGroups(rootGrid);
foreach (var group in listOfVisualStateGroups) Things.Add(new(group));
if (listOfVisualStateGroups?.Count > 0)
foreach (var state in listOfVisualStateGroups[0].States) OtherThings.Add(new(state));
}
}
}
// copy of VisualStateGroup
public partial class Thing1
{
public VisualState CurrentState { get; }
public CoreDispatcher Dispatcher { get; }
public DispatcherQueue DispatcherQueue { get; }
public string Name { get; }
public IList<VisualState> States { get; }
public IList<VisualTransition> Transitions { get; }
public Thing1(VisualStateGroup group)
{
CurrentState = group.CurrentState;
Dispatcher = group.Dispatcher;
DispatcherQueue = group.DispatcherQueue;
Name = group.Name;
States = group.States;
Transitions = group.Transitions;
}
}
// copy of VisualState
public partial class Thing2
{
public CoreDispatcher Dispatcher { get; }
public DispatcherQueue DispatcherQueue { get; }
public string Name { get; }
public SetterBaseCollection Setters { get; }
public IList<StateTriggerBase> StateTriggers { get; }
public Storyboard Storyboard { get; set; }
public Thing2(VisualState state)
{
Dispatcher = state.Dispatcher;
DispatcherQueue = state.DispatcherQueue;
Name = state.Name;
Setters = state.Setters;
StateTriggers = state.StateTriggers;
Storyboard = state.Storyboard;
}
}
<Page
x:Class="TestCollection.Views.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:views="using:TestCollection.Views"
mc:Ignorable="d">
<Grid x:Name="ContentArea">
<StackPanel Orientation="Horizontal">
<ListView Margin="0,0,20,20" BorderThickness="1" BorderBrush="Black" Header="Things (VisualStateGroups)"
ItemsSource="{x:Bind Things}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="views:Thing1">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<ListView Margin="0,0,20,20" BorderThickness="1" BorderBrush="Black" Header="OtherThings (VisualStates)"
ItemsSource="{x:Bind OtherThings}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="views:Thing2">
<TextBlock Text="{x:Bind Name}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</Page>
Any idea why an ObservableCollection<Thing1> works but an ObservableCollection<VisualStateGroup> doesn't?

WPF Image display not updated after source is changed

In my MainView there is a ContentControl which is bound to a CurrentView object. The CurrentView is changed via buttons on the MainView bound to commands.
MainView
<Window>(...)
<RadioButton Content="View1"
Command="{Binding View1Command}"/>
<RadioButton Content="View2"
Command="{Binding View2Command}"/>
<ContentControl Content="{Binding CurrentView}"/>
</Window>
MainVM
(The ObservableObject class implements INotifyPropertyChanged and the RelayCommand class ICommand.)
class MainViewModel : ObservableObject
{
public RelayCommand ViewCommand1 { get; set; }
public RelayCommand ViewCommand2 { get; set; }
public ViewModel2 VM1 { get; set; }
public ViewModel2 VM2 { get; set; }
object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
VM1 = new ViewModel1();
VM1.ContentChanged += (s, e) => OnPropertyChanged();
ViewCommand1 = new RelayCommand(o =>
{
CurrentView = VM1;
});
VM2 = new ViewModel2();
ViewCommand2 = new RelayCommand(o =>
{
CurrentView = VM2;
});
}
}
Those (sub) VM are bound to UserControls which contain image controls and a button to load the image sources from files.
View1
<UserControl x:Class="Project.Views.View1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:viewModels="clr-namespace:Project.ViewModels"
d:DataContext="{d:DesignInstance Type=viewModels:ViewModel1}"
mc:Ignorable="d" >
[...]
<Button Command="{Binding LoadImagesCommand}"/>
[...]
<Image Source="{Binding Images[0]}" "/>
<Image Source="{Binding Images[1]}" "/>
[...]
</UserControl>
VM1
class RiJustageViewModel: ObservableObject
{
public event EventHandler ContentChanged;
void OnContentChanged()
{
ContentChanged?.Invoke(this, new EventArgs());
}
public RelayCommand LoadImagesCommand { get; set; }
public ViewModel1()
{
Images = new BitmapImage[9];
LoadImagesCommand = new RelayCommand(o => LoadImages());
}
BitmapImage[] _images;
public BitmapImage[] Images
{
get { return _images; }
set
{
_images = value;
OnContentChanged();
}
}
public void LoadImages()
{
[...]
for (int i = 0; i < files.Length; i++)
{
Images[i] = Utility.BmImageFromFile(files[i]);
}
[...]
}
}
The issue now is that the images are not shown right away after they are loaded. Only after I change the content of the ContentControl to another view and then back to View1 the images are shown.
Is there a way to trigger that display right after the loading is complete without changing the content of the ContentControl?
EDIT:This should be done everytime the user wants to load new images via the button, not only during initialization.
EDIT:
With lidqy's and EldHasp's comments I was able to clean up the VM and the View using ObservableCollection and ItemsControl.
VM
public class ImageItem
{
public string FileName{ get; set; }
public ImageSource Image { get; set; }
public ImageItem(string f, ImageSource im)
{
FileName = f;
Image = im;
}
}
public ObservableCollection<ImageItem> ImageItems { get; set; }
[...]
public void LoadImages()
{
[...]
ImageItems.Clear();
foreach (var file in files)
{
var im = Utility.BmImageFromFile(file);
var f = Path.GetFileName(file);
ImageItems.Add(new ImageItem(f, im));
}
}
View
<ItemsControl ItemsSource="{Binding ImageItems}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="3" Rows="3"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="18" />
<RowDefinition Height="200" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding FileName}" Style="{StaticResource ImageDescr}" />
<Image Grid.Row="1" Source="{Binding Image}" Style="{StaticResource ImageTheme}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Very neat.
The ContentChanged event is useless.
Declare the Images property like this:
private ImageSource[] images;
public ImageSource[] Images
{
get { return images; }
set
{
images = value;
OnPropertyChanged();
}
}
In LoadImages(), just assign a new array:
public void LoadImages()
{
...
Images = files
.Select(f => Utility.BmImageFromFile(f))
.ToArray();
}
class RiJustageViewModel: ObservableObject
{
public event EventHandler ContentChanged;
void OnContentChanged()
{
ContentChanged?.Invoke(this, new EventArgs());
}
public RelayCommand LoadImagesCommand { get; set; }
public ViewModel1()
{
// If this is not an observable collection,
// then it makes no sense to create it in advance.
// Images = new BitmapImage[9];
LoadImagesCommand = new RelayCommand(o => LoadImages());
}
// Since it is not an observable collection,
// the more general interface can be used: IEnumerable or IEnumerable <T>.
IEnumerable<BitmapImage> _images;
public IEnumerable<BitmapImage> Images
{
get { return _images; }
set
{
_images = value;
OnContentChanged();
}
}
public void LoadImages()
{
[...]
// For an unobservable collection,
// changing elements does not automatically change their presentation.
// We need to create a NEW collection with
// new elements and assign it to the property.
BitmapImage[] localImages = new BitmapImage[files.Length];
for (int i = 0; i < files.Length; i++)
{
localImages[i] = Utility.BmImageFromFile(files[i]);
}
Images = localImages;
[...]
}
}
This implementation has a drawback - it creates a new collection of images each time.
From memory, it doesn't matter (compared to other WPF costs).
But replacing a collection results in a re-creation of the UI elements that represent it.
And this is already significantly longer delays.
For your task, this is also not important, since this happens very rarely.
But for more loaded scenarios, it is better to use an observable collection (INotifyCollectionChanged) or a bindable list (IBindingList).
It is typical for WPF to use ObservableCollection<T>.
But in the case of asynchronous work with them, you need to take measures to work with it was thread-safe.
For the implementation I have shown, thread safety is not needed.
This is already implemented in the Binding mechanism itself to work with the INotifyPropertyChanged interface.
If you want to display all the images of the _images collection of the current view model (which is the "current view") I would display them in a ListBox and put the image tag and the binding into the ListBox's ItemTemplate.
Also as previously mentioned by others, using an ObservableCollecztion<ImageSource> is strongly recommended since your collection data changes and you want your UI to notice it.
If you don't use an ObservableCollection, the view and images get only updated if the view model as a whole changes.

Proper MVVM implementation of dynamically generated usercontrol

My scenario: I have a usercontrol consisting of a comboBox, and a TextBox. The comboBox should hold numbers contained in an ObservableCollection.
The task: The numbers in the ObservableCollection represent paths to book-chapters; therefore each chapter is unique. Meaning: if I have chapters 1 - 5, then the first userControl combo should show all chapters 1-5 (whereas one of them is selected randomly), the second userControl combo contains all chapters, but not the one selected in the previous combo, and so on. The textBox is for annotations for the chapters.
What I achieved so far: I have currently no model; just a main viewModel (ItemsViewModel in my case), and a viewModel for my userControl (PathViewModel). Then there is the mainWindow view.
The problem: On my mainWindow I can create several dynamically created userControls. The userControl TextBox is currently bound to a text property, while the index of the comboBox is bound to another property. But I don't know:
- how to gain access to the index, selected item/value of the specifically userControls
- how to react to a comboBox item/index change
Here is my code:
The userControl
<UserControl> <StackPanel Orientation="Horizontal">
<ComboBox x:Name="combo" Margin="10" MinWidth="60" VerticalAlignment="Center" ItemsSource="{Binding AvailableNumbers}" SelectedIndex="{Binding TheIndex}" />
<TextBox Margin="10" MinWidth="120" Text="{Binding TheText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
The MainWindow
<Window>...<Window.DataContext>
<local:ItemsViewModel/>
</Window.DataContext>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel x:Name="HostPanel">
<ItemsControl ItemsSource="{Binding PathViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:PathControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<StackPanel Grid.Column="1">
<Button Command="{Binding UCCreationCommand}" Content="Add User Control" Margin="10"/>
<Button Command="{Binding UCDeletionCommand}" CommandParameter="" Content="Delete User Control" Margin="10"/>
<Button Command="{Binding ReadoutCommand}" Content="Show ITEMS" Margin="10"/>
</StackPanel>
</Grid></Window>
My main ViewModel (called ItemsViewModel)
public class ItemsViewModel : NotifyPropertyChangedBase
{private int _aNumber;
public int ANumber
{
get { return _aNumber; }
set { _aNumber = value;
OnPropertyChanged(ref _aNumber, value);
}
}
public ObservableCollection<PathViewModel> PathViewModels { get; set; } = new
ObservableCollection<PathViewModel>();
public ObservableCollection<int> AllNumbers { get; set; } = new ObservableCollection<int>();
public ItemsViewModel()
{
UCCreationCommand = new CommandDelegateBase(UCCreationExecute, UCCreationCanExecute);
UCDeletionCommand = new CommandDelegateBase(UCDeletionExecute, UCDeletionCanExecute);
ReadoutCommand = new CommandDelegateBase(ReadoutExecute, ReadoutCanExecute);
AllNumbers.Add(1);
AllNumbers.Add(2);
AllNumbers.Add(3);
AllNumbers.Add(4);
AllNumbers.Add(5);
}
private bool ReadoutCanExecute(object paramerter)
{
if (PathViewModels.Count > 0)
{
return true;
}
return false;
}
private void ReadoutExecute(object parameter)
{
//just for testing
}
public ICommand UCCreationCommand { get; set; }
public ICommand UCDeletionCommand { get; set; }
public ICommand ReadoutCommand { get; set; }
private bool UCCreationCanExecute(object paramerter)
{
if (PathViewModels.Count < 8)
{
return true;
}
else
{
return false;
}
}
private void UCCreationExecute(object parameter)
{
PathViewModel p = new PathViewModel();
foreach (int i in AllNumbers)
{
p.AvailableNumbers.Add(i);
}
int rndIndex = 0;
Random rnd = new Random();
//creates a random chapter index
rndIndex = rnd.Next(0, p.AvailableNumbers.Count);
//just explicit for debugging reasons
p.TheIndex = rndIndex;
AllNumbers.RemoveAt(rndIndex);
PathViewModels.Add(p);
}
private bool UCDeletionCanExecute(object paramerter)
{
if (PathViewModels.Count != 0)
{
return true;
}
else
{
return false;
}
}
private void UCDeletionExecute(object parameter)
{
PathViewModel p = new PathViewModel();
int delIndex = PathViewModels.Count - 1;
p = PathViewModels[delIndex];
AllNumbers.Add((int)p.TheValue+1);
PathViewModels.Remove(p);
}
}
And finally my UserControl ViewModel:
public class PathViewModel : NotifyPropertyChangedBase
{
public ObservableCollection<int> AvailableNumbers { get; set; } = new ObservableCollection<int>();
private int _theIndex;
public int TheIndex
{
get { return _theIndex; }
set
{
_theIndex = value;
OnPropertyChanged(ref _theIndex, value);
}
}
private int _theValue;
public int TheValue
{
get { return _theValue; }
set
{
_theValue = value;
OnPropertyChanged(ref _theValue, value);
}
}
private string _theText;
public string TheText
{
get { return _theText; }
set
{
_theText = value;
OnPropertyChanged(ref _theText, value);
}
}
public PathViewModel()
{
}
}
Any hints on how to go on from here would be highly appreaciated.

Change content of UserControl from Page code behind

So I have a user control:
<StackPanel Orientation="Vertical"
Margin="10">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Stretch"
Margin="10">
<TextBlock Text="{x:Bind FileName, Mode=OneTime}"
HorizontalAlignment="Left"/>
<TextBlock Text="{x:Bind DownloadSpeed, Mode=OneWay}"
HorizontalAlignment="Right"/>
</StackPanel>
<ProgressBar Name="PbDownload"
HorizontalAlignment="Stretch" />
<TextBlock Text="{x:Bind DownloadCompletePercent, Mode=OneWay}"/>
</StackPanel>
User control code behind:
public sealed partial class UCDownloadCard : UserControl
{
public UCDownloadCard()
{
this.InitializeComponent();
}
public string FileName { get; set; }
public string DownloadSpeed { get; set; }
public string DownloadCompletePercent { get; set; }
}
What I am trying to do is to show file download status using this user control. Whenever a new download is started, I want to programmatically add a new user control and then update the values in it as the download happens.
Currently I am doing something like this:
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
public CancellationTokenSource CancellationTokenSource { get; set; }
public List<DownloadOperation> ActiveDownloads { get; set; } = new List<DownloadOperation>();
public List<UCDownloadCard> AddedCards { get; set; } = new List<UCDownloadCard>();
private async Task HandleDownloadAsync(DownloadOperation downloadOperation, CancellationToken cancellationToken = new CancellationToken())
{
ActiveDownloads.Add(downloadOperation);
...
...
try
{
AddDownloadProgressCard();
await downloadOperation.StartAsync().AsTask(CancellationTokenSource.Token, progressCallback);
}
finally
{
...
...
}
}
private void AddDownloadProgressCard()
{
var card = new UCDownloadCard
{
Name = $"Card{AddedCards.Count}",
FileName = "Filename.pdf",
DownloadCompletePercent = "0% completed",
DownloadSpeed = "0 KB/s"
};
AddedCards.Add(card);
OutputArea.Children.Add(card);
}
private void DownloadProgressChanged(DownloadOperation downloadOperation)
{
var downloadPercent = 100 * ((double)downloadOperation.Progress.BytesReceived / (double)downloadOperation.Progress.TotalBytesToReceive);
this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High, () =>
{
AddedCards[0].DownloadCompletePercent = downloadPercent.ToString();
Debug.WriteLine($"Updating Progress: {downloadPercent}%");
});
}
}
I am able to add the UserControl to the OutputArea but the values in it are not updating. But I am sure that the AddedCards[0].DownloadCompletePercent = downloadPercent.ToString(); is being executed multiple times because the Debug.WritLine just below it is actually printing to the output window.
How can I update the values in the UserControl ?
Firstly, you should change your UserControl with x:Bind Mode=TwoWay.See {x:Bind} markup extension for more details.
Then you should implement the INotifyPropertyChanged interface and implement the PropertyChanged event. The code you can refer the PropertyChanged event.
Here is a simple sample, you can have a reference.
UserControl.xaml,
<StackPanel Orientation="Vertical"
Margin="10">
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Stretch"
Margin="10">
<TextBlock Text="{x:Bind DownloadCompletePercent, Mode=TwoWay}"/>
</StackPanel>
UserControl.xaml.cs,
public sealed partial class UCDownloadCard : UserControl, INotifyPropertyChanged
{
public UCDownloadCard()
{
this.InitializeComponent();
}
private string downloadCompletePercent;
public string DownloadCompletePercent
{
get
{
return downloadCompletePercent;
}
set
{
downloadCompletePercent = value;
RaisePropertyChanged("DownloadCompletePercent");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Then you can add this UserControl and update its downloadCompletePercent,
In the MainPage.xaml.cs,
private void DownloadProgress(DownloadOperation obj)
{
BackgroundDownloadProgress currentProgress = obj.Progress;
double percent;
if (currentProgress.TotalBytesToReceive > 0)
{
percent = currentProgress.BytesReceived * 100 / currentProgress.TotalBytesToReceive;
Debug.WriteLine(percent);
uCDownloadCard.DownloadCompletePercent = percent.ToString();
}
}
UCDownloadCard uCDownloadCard;
private void Button_Click_2(object sender, RoutedEventArgs e)
{
uCDownloadCard = new UCDownloadCard();
MainPagePanel.Children.Add(uCDownloadCard);
}

How to notify XAML properties when list data of binding changed?

I'm using the following code for binding
XAML
<StackPanel x:Name="channelsRecordTimeData" Orientation="Vertical">
<ItemsControl x:Name="channelRecordTimeItems" ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="gridChannelRecordTimeItem" Width="{Binding Path=ChannelRecordTimeItemWidth}"
Height="{Binding Path=ChannelRecordTimeItemHeight}" Margin="{Binding Path=ChannelRecordTimeItemsMargin}"
HorizontalAlignment="Left" DataContext="{Binding Path=ListRecordTime}">
<Grid.Background>
<ImageBrush x:Name="gridChannelRecordTimeItemBgr" ImageSource="..\Resources\playback_grid_channel_record_time_item_bgr_normal.png"/>
</Grid.Background>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
C#
public class DATA
{
public double ChannelRecordTimeItemWidth { set; get; }
public double ChannelRecordTimeItemHeight { set; get; }
public Thickness ChannelRecordTimeItemsMargin { set; get; }
public List<RecordTime> ListRecordTime { set; get; }
public DATA()
{
ChannelRecordTimeItemWidth = 1000;
ChannelRecordTimeItemHeight = 20;
ChannelRecordTimeItemsMargin = new System.Windows.Thickness(0, 0, 0, 0);
ListRecordTime = null;
}
}
public static List<DATA> listDATA = new List<DATA>();
for(int i = 0 ; i < 10 ; i++)
{
DATA data = new DATA();
listDATA.Add(data);
}
channelRecordTimeItems.ItemsSource = listDATA;
channelRecordTimeItems.Items.Refresh();
This code will notify to the XAML update when I use the line of code as
listDATA[0].ChannelRecordTimeItemWidth -= 15;
There is any way to XAML update properties automatically, when we manipulate on the listDATA as
listDATA.RemoveAt();
listDATA.Add();
listDATA.Clear();
Without calling the two following lines code
channelRecordTimeItems.ItemsSource = listDATA;
channelRecordTimeItems.Items.Refresh();
GUI will be updated only in case underlying source collection is implementing INotifyCollectionChanged which raise CollectionChanged events to refresh GUI components.
You can use ObservableCollection which internally provides you this feature.
Replace
public static List<DATA> listDATA = new List<DATA>();
with
public static ObservableCollection<DATA> listDATA = new ObservableCollection<DATA>();

Categories