I have test app which adding items to ListBox in loop. Now, ListBox updates, when all items added to ObservableCollection. I need to update ListBox when every item added. Please, help russian guy =)
Here is my code:
MainWindow.xaml
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="432,288.04,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
<ListBox x:Name="urlsListBox" ItemsSource="{Binding Urls}" HorizontalAlignment="Left" Height="300" Margin="10,10,0,0" VerticalAlignment="Top" Width="417"/>
</Grid>
MainWindow.xaml.cs
/// <summary>
/// Логика взаимодействия для MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
ViewModel model = new ViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = model;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
model.GetUrls();
}
}
ViewModel.cs
class ViewModel
{
public ObservableCollection<Url> Urls { get; set; }
public ViewModel()
{
Urls = new ObservableCollection<Url>();
}
public void GetUrls()
{
for (int i = 0; i < 5; i++)
{
Urls.Add(new Url { link = i.ToString() });
//Thread.Sleep(300);
}
}
}
public class Url
{
public string link { get; set; }
}
I think you need to notify the UI about the change happening in the URL observable collection each time it adds a new item.
If you have implemented INotifyPropertyChanged in your view model then register the observable collection with any change happening in it.
private ObservableCollection<Url> _urls;
public ObservableCollection<Url> Urls
{ get
{
return _urls;
}
set
{
_urls = value;
OnPropertyModified("Urls");
}
}
You want UI to update on every item addition in collection i guess.
Looking at Thread.Sleep(300), i am assuming you want to wait for 300 milliseconds before adding another item to collection.
DispatcherTimer is what you are looking for then. This sample will work:
public void GetUrls()
{
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = new TimeSpan(0, 0, 0, 0, 300);
int i = 0;
timer.Tick += (s, e) =>
{
Urls.Add(new Url { link = i.ToString() });
++i;
if (i == 5)
timer.Stop();
};
timer.Start();
}
This will add item in collection after every 300 ms and will stop timer once item count reaches to 5.
Like i mentioned in comment
Dispatcher associated with UI thread can process one operation at a
time. When it's adding item to collection, it can't process Rendering
operation at same time. Hence GUI not getting refreshed. Usage of DispatcherTimer gives some time to rendering operations to get processed.
Another approach without DispatcherTimer would be to enqueue empty delegate on UI dispatcher with Render priority so that all operations with priority higher than Render (including Render) gets processed before moving to addition of another item in collection.
public void GetUrls()
{
for (int i = 0; i < 5; i++)
{
Urls.Add(new Url { link = i.ToString() });
App.Current.Dispatcher.Invoke((Action)(() => { }),
DispatcherPriority.Render);
}
}
Related
There is a page where the user selects parameters to show the proper collection then on button click jumps to the next page (Coll) where it should show up.
User Selection Page XAML:
<ContentPage.BindingContext><xyz:UserSelectionViewModel</ContentPage.BindingContext>
...
<Button x:Name="Start" Command="{Binding LoadData}" Pressed="StartClick"/>
User Selection Page C#:
private async void ButtonClick(object sender, EventArgs e)
{
var vm = (CollViewModel)BindingContext;
vm.Hard = HardButtonSelected == Hard;
...
vm.Subject = vm.Subject.ToLower();
}
UserSelectionViewModel:
public class UserSelectionViewModel : BaseViewModel
{
public UserSelectionViewModel()
{
_dataStore = DependencyService.Get<IDataStore>();
_pageService = DependencyService.Get<IPageService>();
LoadData= new AsyncAwaitBestPractices.MVVM.AsyncCommand(FilterData);
FilteredData = new ObservableRangeCollection<Items>();
}
public async Task FilterData()
{
FilteredData.Clear();
var filtereddata = await _dataStore.SearchData(Hard, Subject).ConfigureAwait(false);
FilteredData.AddRange(filtereddata);
OnPropertyChanged("FilteredData");
Debug.WriteLine(FilteredData.Count());
await Device.InvokeOnMainThreadAsync(() => _pageService.PushAsync(new Coll(FilteredData)));
}
}
Coll XAML:
<ContentPage.BindingContext><xyz:CollViewModel</ContentPage.BindingContext>
...
<CarouselView ItemsSource="{Binding Source={RelativeSource AncestorType={x:Type z:Coll}}, Path=InheritedData}" ItemTemplate="{StaticResource CollTemplateSelector}">
...
Coll C#:
public partial class Coll : ContentPage
{
public ObservableRangeCollection<Feladatok> InheritedData { get; set; }
public Coll(ObservableRangeCollection<Feladatok> x)
{
InitializeComponent();
InheritedData = x;
OnPropertyChanged(nameof(InheritedData));
}
}
CollViewModel:
public class CollViewModel : UserSelectionViewModel { ... }
BaseViewModel:
private ObservableRangeCollection<Feladatok> inheriteddata;
public ObservableRangeCollection<Feladatok> InheritedData
{
get
{
return inheriteddata;
}
set
{
if (value != inheriteddata)
{
inheriteddata = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("InheritedData"));
}
}
}
Managed to make it work like this with the help of Jason's tips. My only concern remaining is that: Won't this slow down the page that I load the observable collection two times basically? Is it a good practice as I have made it?
Eventually set the BindingContext to the VM and Binding from there. I still feel like it could be done more efficently or maybe that's how it is done. ViewModels are still new for me and I feel like it's much more code and slower with it. But I will close this, as it is working now.
I want to build some count down counter. The problem is that my solution display only beginning value 10 and last value 1 after 10 sec. Of course, I've implemented the INotifyPropertyChanged interface. Any suggestions for that solution?
<Button Content="Generuj" Command="{Binding ButtonStart}"></Button>
<TextBox Text="{Binding Counter, Mode=OneWay}"></TextBox>
private void ButtonStartClick(object obj)
{
for (int i = 10; i > 0; i--)
{
System.Threading.Thread.Sleep(1000);
Counter = i;
}
}
With Thread.Sleep you are freezing your GUI. Try using a Timer for your purpose.
A timer will run simultaneously to your GUI thread and thus will not freeze it.
Also you will need to implement the PropertyChanged Event for your counter
Also make sure to set your DataContext
//create a dependency property you can bind to (put into class)
public int Counter
{
get { return (int)this.GetValue(CounterProperty); }
set { this.SetValue(CounterProperty, value); }
}
public static readonly DependencyProperty CounterProperty =
DependencyProperty.Register(nameof(Counter), typeof(int), typeof(MainWindow), new PropertyMetadata(default(int)));
//Create a timer that runs one second and decreases CountDown when elapsed (Put into click event)
Timer t = new Timer();
t.Interval = 1000;
t.Elapsed += CountDown;
t.Start();
//restart countdown when value greater one (put into class)
private void CountDown(object sender, ElapsedEventArgs e)
{
if (counter > 1)
{
(sender as Timer).Start();
}
Counter--;
}
You can also run Counter in separate Thread
Task.Run(() =>
{
for (int i = 10; i > 0; i--)
{
Counter = i;
Thread.Sleep(1000);
}
});
You can use async await to introduce a lightweight delay.
It's main advantage over a timer is there's no risk of leaving the delegate hooked and running.
In this viewmodel I use mvvmlight, but any implementation of ICommand would do.
…..
using System.Threading.Tasks;
using GalaSoft.MvvmLight.CommandWpf;
namespace wpf_99
{
public class MainWindowViewModel : BaseViewModel
{
private int counter =10;
public int Counter
{
get { return counter; }
set { counter = value; RaisePropertyChanged(); }
}
private RelayCommand countDownCommand;
public RelayCommand CountDownCommand
{
get
{
return countDownCommand
?? (countDownCommand = new RelayCommand(
async () =>
{
for (int i = 10; i > 0; i--)
{
await Task.Delay(1000);
Counter = i;
}
}
));
}
}
Not much to the view, it binds to Counter, of course:
<Grid>
<StackPanel>
<TextBlock Text="{Binding Counter}"/>
<Button Content="Count" Command="{Binding CountDownCommand}"/>
</StackPanel>
</Grid>
I got some problem in showing download percentage in GridView of WCF. I used MVVM pattern.
Here is my background worker in application start:
public partial class MainWindow : Window
{
public MainWindow()
{
Overall.EverythingOk = "Nothing";
InitializeComponent();
//IRepo repo = new Repo();
ViewModel.MainWindowsViewModel viewModel = new ViewModel.MainWindowsViewModel();
this.DataContext = viewModel;
BackGroundThread bgT = new BackGroundThread();
bgT.bgWrk.RunWorkerAsync();
}}
Here is the DoWork function in BackGroundTHread class
public void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (!Overall.stopStatus)
{
for (int i=0; i < 10000; i++)
{
Overall.PercentageDwnd = i;
Overall.caseRefId = "999999";
if (i == 9998)
{
i = 1;
}
}
}
}
Overall.PercentageDwnd and Overall.caseRefId are static variable (you can call from everywhere in the application) and always update until the background worker completed. I got another ViewModel called TestViewModel and here it is.
public class TestViewModel:BindableBase
{
private String _UpdatePer=Overall.PercentageDwnd.ToString();
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
private ObservableCollection _ViewAKA = new ObservableCollection();
private tblTransaction model;
public TestViewModel(tblTransaction model)
{
// TODO: Complete member initialization
}
public ObservableCollection ViewAKA
{
get { return _ViewAKA; }
set { SetProperty(ref _ViewAKA, value); }
}
}
I bind with TestView.xaml file
<Window x:Class="EmployeeManager.View.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestView" Height="359.774" Width="542.481">
<Grid Margin="0,0,2,0">
<Label Content="{Binding UpdatePercentage,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Background="Red" Foreground="White" Margin="130,86,0,0" VerticalAlignment="Top" Width="132" Height="39">
</Label>
</Grid>
</Window>
There is no real time update at Label even though I bind UpdatePercentage to it. How can I update real time to label?
The problem is that you are updating the static properties, which are not bound to anything. You need to update and raise the property changed notification for the properties which are bound to the label controls, i.e. UpdatePercentage
Can you pass the TestViewModel instance into the RunWorkerAsync call?
bgT.bgWrk.RunWorkerAsync(testViewModel);
And then access in the DoWork event handler:
public void bw_DoWork(object sender, DoWorkEventArgs e)
{
if (!Overall.stopStatus)
{
var viewModel = e.Argument as TestViewModel;
for (int i=0; i < 10000; i++)
{
Overall.PercentageDwnd = i;
viewModel.UpdatePercentage = i;
Overall.caseRefId = "999999";
if (i == 9998)
{
i = 1;
}
}
}
}
Here is answer link:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/02a7b9d1-1c26-4aee-a137-5455fee175b9/wpf-percentage-status-shown-in-label-mvvm?forum=wpf
i need to trigger when the Overall.PercentageDwnd property changes.
Edited
In Overall Class:
public class Overall
{
private static int _percentage;
public static int PercentageDwnd
{
get { return _percentage; }
set
{
_percentage = value;
//raise event:
if (PercentageDwndChanged != null)
PercentageDwndChanged(null, EventArgs.Empty);
}
}
public static string caseRefId { get; set; }
public static bool stopStatus { get; set; }
public static event EventHandler PercentageDwndChanged;
}
In TestViewModel:
public class TestViewModel : BindableBase
{
private String _UpdatePer = Overall.PercentageDwnd.ToString();
public String UpdatePercentage
{
get { return _UpdatePer; }
set { SetProperty(ref _UpdatePer, value); }
}
public TestViewModel(tblTransaction model)
{
Overall.PercentageDwndChanged += Overall_PercentageDwndChanged;
// TODO: Complete member initialization
}
private void Overall_PercentageDwndChanged(object sender, EventArgs e)
{
this.UpdatePercentage = Overall.PercentageDwnd.ToString();
}
}
Since you have bound the TextBlock in the view to the UpdatePercentage source property, you need to set this one and raise the PropertyChanged event whenever you want to update the Label in the view. This means that you need to know when the Overall.PercentageDwnd property changes.
Credit to
Magnus (MM8)
(MCC, Partner, MVP)
Thanks All
I am having issues updating a separately opened window's progress bar from a background worker inside another class.
The program execution goes like this:
MainWindow loads
Click button to do some work and open a popup
progress bar (newly opened window)
Background worker does work
and reports progress to popup progress bar
Popup progress bar
hopefully updates.
The progress bar Value is bound to a property, which in the step-through debugger, looks to be getting updated okay by the background worker. These changes just are not reflected on the popup progress bar view. However, the binding is not broken because if I manually try and set the property value for the progress bar it works fine.
Furthermore, when I put the progress bar inside the initially started MainWindow view it updates fine. Any suggestions??
Here is the some code:
MainWindowViewModel
public class MainWindowViewModel: BaseViewModel
{
private void PerformSomeAction()
{
var popUpProgressBar = new PopUpProgressBarViewModel();
popUpProgressBar.Show(popUpProgressBar);
var worker = new BackgroundWorker { WorkerReportsProgress = true };
worker.ProgressChanged += delegate(object s, ProgressChangedEventArgs args)
{
if (args.ProgressPercentage != popUpProgressBar.Progresser)
{
Progresser = args.ProgressPercentage;
popUpProgressBar.Progresser = args.ProgressPercentage;
}
};
worker.DoWork += delegate
{
for (int i = 0; i < 101; i++)
{
worker.ReportProgress(i);
System.Threading.Thread.Sleep(10);
}
MessageBox.Show("Done");
};
worker.RunWorkerAsync();
}
private int _progresser;
public int Progresser
{
get { return _progresser; }
set
{
if (_progresser == value) return;
_progresser = value;
OnPropertyChanged("Progresser");
}
}
private RelayCommand _startProcessing; //set private member
public ICommand StartProcessing //public field used by xaml binding
{
get
{
return _startProcessing = MakeCommandSafely(_startProcessing, () => PerformSomeAction());
}
}
}
PopUpProgressBarViewModel
public class PopUpProgressBarViewModel : BaseViewModel
{
private PopUpProgressBar _popUpProgressBar;
public void Show(PopUpProgressBarViewModel context)
{
_popUpProgressBar = new PopUpProgressBar {DataContext = context};
_popUpProgressBar.Show();
}
private int _progresser;
public int Progresser
{
get { return _progresser; }
set
{
if (_progresser == value) return;
_progresser = value;
OnPropertyChanged("Progresser");
}
}
}
For full solution file (so you can see whats happening), see here
As #Doug said, since you are already setting the DataContext:
_popUpProgressBar = new PopUpProgressBar {DataContext = context};
You can change the PopUpProgressBar to
<Window x:Class="OpeningWindow_With_ProgressBar.View.PopUpProgressBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:OpeningWindow_With_ProgressBar.ViewModel" Title="PopUpProgressBar" Height="150" Width="300">
<Grid>
<StackPanel>
<Label FontWeight="Bold">Loading Something</Label>
<ProgressBar Minimum="0" Maximum="100" Margin="0,10,0,0" Height="25px" Width="250px" Value="{Binding Path=Progresser, Mode=OneWay}"></ProgressBar>
<TextBlock Margin="10,10,0,0" Text="Details of loading..."></TextBlock>
</StackPanel>
</Grid>
You are creating two PopUpProgressBarViewModels. You've got one that's being created as a resource inside PopUpProgressBar.xaml, and the other one is being created in MainWindowViewModel (line 18).
Your XAML is bound to the one created inside PopUpProgressBar.xaml, while the one that you're updating is the one created in MainWindowViewModel.
If you can pare it down so only one is created, that should solve your problem.
So I've got a TimerModel which holds data for the entity and a TimerViewModel which handles the timer ticks from each one of the timers. It simply decrements the TimeSpan set as the interval for the timer - I'm doing this purely because I need to update the View with the remaining time.
public class TimerModel
{
public TimerModel(TimeSpan Interval, TimerViewModel Parent)
{
Timer = new DispatcherTimer();
Timer.Interval = TimeSpan.FromSeconds(1);
parent = Parent;
this.Interval = Interval;
Timer.Tick += (sender, args) => parent._timer_Tick(this, args);
Timer.Start();
}
private TimerViewModel parent;
public DispatcherTimer Timer
{
get;
private set;
}
public TimeSpan Interval
{
get;
set;
}
}
And the TimerViewModel would be:
public class TimerViewModel : ViewModelBase
{
public TimerViewModel()
{
Timers = new ObservableCollection<TimerModel>();
AddTimerCommand = new RelayCommand((object x) => { AddTimer(x); });
}
public ObservableCollection<TimerModel> Timers
{
get;
private set;
}
public ICommand AddTimerCommand
{
get;
private set;
}
private void AddTimer(object s)
{
Timers.Add(new TimerModel(TimeSpan.FromSeconds(250), this));
}
internal void _timer_Tick(object sender, EventArgs e)
{
TimerModel timer = sender as TimerModel;
timer.Interval = timer.Interval.Subtract(TimeSpan.FromSeconds(1));
RaisePropertyChanged("Timers");
}
}
Which is bound to a listbox:
<ListBox x:Name="lst_timers" Height="300" Width="150" HorizontalAlignment="Left"
DataContext="{Binding local:TimerViewModel}" ItemsSource="{Binding Timers}"
DisplayMemberPath="Interval" />
_timer_Tick should be raising the property changed for the whole list, but seemingly the listbox items remain unchanged.
Can anyone give me a clue what am I missing here?
The Timers list is not changing, it's the Interval property of the Timer object that changes.
I'd suggest a ViewModel for your whole view (set as DataContext for your window/control..., not for the ListBox inside this parent window/control) containing a property Timers of type ObervableCollection<TimerViewModel>. This TimerViewModel represents one single timer and should raise a PropertyChanged-Event for "Interval" in the property's setter.
(For additional information see comments)
<ListBox x:Name="lst_timers" ...>
<ListBox.DataContext>
<local:TimerViewModel/>
</ListBox.DataContext>
</ListBox>