How to use Progressbar in MVVM - c#

I'm really stuck on this. I searched a lot in the net but actually nothing helped me. In my view I have one button and the progressbar, when the user click on button it does different work and after increase the CurrentProgress.
But it shows to user only in the end of Work the 100% in the end and is not what i want. I would like that eachtime that the CurrentProgress is increased it shows and display to the view.
The controls in the view are those:
<Button x:Name="Generate" Content="Generate" />
<ProgressBar Value="{Binding Path=CurrentProgress, Mode=OneWay}" Width="80" Height="15"/>
And this is the code in the ViewModel
private int currentProgress;
public int CurrentProgress
{
get { return currentProgress; }
set
{
if (currentProgress == value)
return;
currentProgress = value;
NotifyOfPropertyChange(() => CurrentProgress);
}
}
List<Article> articles;
public void Generate()
{
foreach (var art in articles)
{
//[..]
//Insert article
inserted++;
Task.Factory.StartNew(() =>updateProgress(inserted));
}
}
private void updateProgress(int Analyzed)
{
if (Analyzed != 0)
{
int percentage = 100 * Analyzed / articles.Count;
CurrentProgress = percentage;
}
}
How to fix the issue?. Thanks to all in advance!

Put the whole content of the "Generate" method inside a task. Then you don't need to do anything special to update the progress since the "NotifyPropertyChanged" events are automatically marshalled at UI level by WPF.
Something like this:
Task.Factory.StartNew(() => {
foreach (var art in articles)
{
//[..]
//Insert article
inserted++;
updateProgress(inserted);
}
}

Related

Calculating percentage in a column on Blazorise datagrid

I have a Blazor app that I am working on for sales and I am trying to work out a column for percentage. This column is not stored in the database as the calculation should happen on the web form. I cannot seem to figure out where I am going wrong, I get a selectedItem is null error when I run my page.
my razor page:
<DataGrid TItem="SalesTable"
Data="#sales"
Sortable="true"
Filterable="false"
Editable="true"
ShowPager="true"
PageSize="10"
UseInternalEditing="true"
EditMode="DataGridEditMode.Inline"
RowUpdated="#OnRowUpdated"
RowInserted="#OnRowInserted"
RowRemoved="#OnRowRemoved"
NewItemDefaultSetter="#OnNewItemDefaultSetter"
#bind-SelectedRow="#selectedItem"
Striped="true"
Bordered="true"
Hoverable="true"
Responsive="true"
>
#{
decimal percentagecalc = selectedItem.salesvalue/sumOfSales*100;
}
<DataGridNumericColumn Field=#nameof(#percentagecalc) Caption="Percentage" Editable="false"></DataGridNumericColumn>
</DataGrid>
#code
{
private List<SalesTable> sales;
private SalesTable selectedItem;
string selectedDropValue { get; set; }
private decimal sumOfSales;
protected override async Task OnInitializedAsync()
{
budgets = await SalesService.GetSalesTablesAsync();
sumOfSales=Sales.Sum(f => f.salesvalue);
}
}
does anyone know what I am doing wrong?
You can use DisplayTemplate to create a calculated column.
<DataGrid TItem="SalesTable"
Data="#sales"
...
...
...>
<DataGridNumericColumn Field=#nameof(SalesTable.salesvalue) Caption="Percentage" Editable="false">
<DisplayTemplate>
#{
decimal percentagecalc = context.salesvalue / sumOfSales * 100;
}
#percentagecalc
</DisplayTemplate>
</DataGridNumericColumn>
</DataGrid>
#code
{
private List<SalesTable> sales;
private SalesTable selectedItem;
string selectedDropValue { get; set; }
private decimal sumOfSales;
protected override async Task OnInitializedAsync()
{
budgets = await SalesService.GetSalesTablesAsync();
sumOfSales=Sales.Sum(f => f.salesvalue);
}
}
Documentation for Blazorise DataGrid templates: https://preview.blazorise.com/docs/extensions/datagrid/templates
I noticed that there is an EditTemplate tag, which might help.
<DataGridColumn Field="#nameof(SalesTable.salesvalue)" Caption="Percentage" Editable="false">
<EditTemplate>
<NumericEdit TValue="decimal" Value="#((decimal)context.CellValue)" ValueChanged="#( v => context.CellValue = v * 100 / sumOfSales)" />
</EditTemplate>
</DataGridColumn>
And you may develop the usages of context.

How to await the generation of containers in an ItemsControl?

I have a SettingsWindow, in it there is an audio file selector which has a context menu. Some code accesses the MyAudioFileSelector computed property before it can get the AudioFileSelector because the AudioFileSelector is just inside a DataTemplate of an item in an ItemsControl that has not yet generated its containers at that moment. I tried to defer the access to MyAudioFileSelector using Dispatcher.BeginInvoke with DispatcherPrority.Loaded, but the item containers are still not yet generated at that moment.
The code that accesses the MyAudioFileSelector is the method that applies one of the many settings inside the user-selected data file. This method is called from the Window's Loaded event handler synchronously for each setting in the program's data files' schema.
I am very new to async-await programming, I have read this but I am not sure how this helps me, and I read this page but I am still not sure what to do. I have read this a but the only answer, unaccepted, seems similar to what I already use below:
MySettingsWindow.Dispatcher.BeginInvoke(new Action(() =>
{
[...]
}), System.Windows.Threading.DispatcherPriority.Loaded);
A part of the XAML
(InverseBooleanConv just makes true, false, and false, true)
<ItemsControl Grid.ColumnSpan="3" Margin="0,0,-0.6,0" Grid.Row="0"
ItemsSource="{Binding SettingsVMs}" x:Name="MyItemsControl">
<ItemsControl.Resources>
<xceed:InverseBoolConverter x:Key="InverseBooleanConv"/>
<DataTemplate DataType="{x:Type local:AudioFileSettingDataVM}">
<local:AudioFileSelector MaxHeight="25" Margin="10" FilePath="{Binding EditedValue, Mode=TwoWay}">
<local:AudioFileSelector.RecentAudioFilesContextMenu>
<local:RecentAudioFilesContextMenu
PathValidationRequested="RecentAudioFilesContextMenu_PathValidationRequested"
StoragePropertyName="RecentAudioFilePaths"
EmptyLabel="No recent audio files."/>
</local:AudioFileSelector.RecentAudioFilesContextMenu>
</local:AudioFileSelector>
</DataTemplate>
[...]
Parts of the code-behind
In MainWindow.xaml.cs, the beginning of the Window_Loaded handler
private void Window_Loaded(object sender, RoutedEventArgs e)
{
VM.ClockVMCollection.Model.FiltersVM.Init();
VM.Settings.IsUnsavedLocked = true;
VM.ClockVMCollection.Model.IsUnsavedLocked = true;
foreach (KeyValuePair<string, SettingDataM> k in VM.Settings)
{
ApplySetting(k.Value);
}
[...]
In MainWindow.xaml.cs, in the method ApplySetting
case "AlwaysMute":
VM.MultiAudioPlayer.Mute = (bool)VM.Settings.GetValue("AlwaysMute");
break;
case "RecentAudioFilePaths":
MySettingsWindow.Dispatcher.BeginInvoke(new Action(() =>
{
MySettingsWindow.MyRecentAudioFilesContextMenu. // here, MyRecentAudioFilesContextMenu is null, this is the problem
LoadRecentPathsFromString(VM.Settings.GetValue("RecentAudioFilePaths") as string);
}), System.Windows.Threading.DispatcherPriority.Loaded);
break;
case "RecentImageFilePaths":
MySettingsWindow.Dispatcher.BeginInvoke(new Action(() =>
{
MySettingsWindow.MyRecentImageFilesContextMenu. // here, MyRecentImageFilesContextMenu is null, this is the problem
LoadRecentPathsFromString(
VM.Settings.GetValue("RecentImageFilePaths") as string);
}), System.Windows.Threading.DispatcherPriority.Loaded);
break;
[...]
In the SettingsWindow class
internal AudioFileSelector MyAudioFileSelector
{
get
{
foreach (SettingDataVM vm in MyItemsControl.ItemsSource)
{
if (vm is AudioFileSettingDataVM)
{
return (AudioFileSelector)MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm);
}
}
return null;
}
}
internal ImageFileSelector MyImageFileSelector
{
get
{
foreach (SettingDataVM vm in MyItemsControl.ItemsSource)
{
if (vm is ImageFileSettingDataVM)
{
return (ImageFileSelector)MyItemsControl.ItemContainerGenerator.ContainerFromItem(vm);
}
}
return null;
}
}
internal RecentAudioFilesContextMenu MyRecentAudioFilesContextMenu
{
get
{
return MyAudioFileSelector?.RecentAudioFilesContextMenu;
}
}
internal RecentFilesContextMenu MyRecentImageFilesContextMenu
{
get
{
return MyImageFileSelector?.RecentImageFilesContextMenu;
}
}
The bug is in the two C# comments in one of the code snippets above, null reference exceptions.
I think I could attach in the MainWindow a handler to SettingsWindow's ItemsControl's ItemContainerGenerator's StatusChanged event and then continue the initialization of the window, including the loading of all the settings, but I wonder if there is a more orderly/correct way.
Thank you.
If you have access to your ItemsControl in the code-behind under the variable name MyItemsControl, then you can add an event handler for the ContainerGenerator StatusChanged event:
private void Window_Loaded(object sender, RoutedEventArgs e) {
//Subscribe to generated containers event of the ItemsControl
MyItemsControl.ItemContainerGenerator.StatusChanged += ContainerGenerator_StatusChanged;
}
/// <summary>
/// Handles changed in container generator status.
///</summary>
private void ContainerGenerator_StatusChanged(object sender, EventArgs e) {
var generator = sender as ItemContainerGenerator;
//Check that containers have been generated
if (generator.Status == GeneratorStatus.ContainersGenerated ) {
//Do stuff
}
}
I really recommand not to use this if what you're after is simply save/load data from a file, as they are completely unrelated.

Get WPF Slider value after dragging

I'm trying to get WPF Slider value after the user finishing to drag the thumb or clicking to move to specific point.
i want to save the new value in db by listening to some kind of event. how can i do this?
i tried the solutions on this question but i end up with nothing -
WPF: Slider with an event that triggers after a user drags
each time it enters the event lots of times
thanks.
xaml:
<Slider Value="{Binding VoipVolume}" MouseLeftButtonUp="slider_MouseLeftButtonUp"/>
codebehind:
public double VoipVolume
{
get { return (double)GetValue(VoipVolumeProperty); }
set
{
SetValue(VoipVolumeProperty, value);
VolumeChanged(value);
}
}
private void VolumeChanged(object value)
{
StationViewModel viewModel = this.DataContext as StationViewModel;
if (viewModel != null)
{
if (end)
{
viewModel.OnVolumeChange((float)VoipVolume);
end = false;
}
}
}
bool end = false;
private void slider_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
end = true;
}
Possibly duplicate. hope this may help you out: stackoverflow.com/a/723547/1611490
<Slider Thumb.DragCompleted="MySlider_DragCompleted" />
and under MySlider_DragCompleted event, update your property of the VM or get the value.
if your are using FW 4.5 or above, try using the "Dalay" property.
http://www.jonathanantoine.com/2011/09/21/wpf-4-5-part-4-the-new-bindings-delay-property/
We didn't have a choice, in the end we use TickFrequency because we need to save the changes in the slider to db, and we don't want it to happen for every 0.001..
thanks anyway..

Updating TextBlock at start of code block in MainWindow asynchronously

I have a problem that's been bugging me for days, I've tried every option and I'm now resulting in posting my own question to find some specific help from you guys.
I need to update a TextBlock at the start of code block, which is run on a simple button click.
Here's my code:
private void NewProject(bool blnCopy = false, string strFileName = null)
{
if (App.ApplicationBusy == false)
{
App.ApplicationBusy = true;
try
{
Action action = delegate()
{
Cursor = Cursors.Wait;
lblStatus.Text = "Opening Project...";
};
Dispatcher.Invoke(DispatcherPriority.Send, action);
if (blnCopy == false) { Project = new GSProject((App.RecentProjectCount + 1)); }
if (Project != null)
{
Projects.Add(Project);
if (blnCopy == false)
{
if (strFileName == null)
{
Project.ProjectName = string.Format("GSProject{0}", Projects.Count.ToString());
Project.ProjectDescription = string.Format("{0} - HW GS Project", Project.ProjectName);
Project.LoadResource();
}
else
{
Project.Load(strFileName);
}
}
else
{
Project = Project.Copy();
}
p_objNewPane = objDocker.AddDocument(Project.ProjectDisplayName, Project);
if (p_objNewPane != null)
{
p_objNewPane.DataContext = Project;
BindingOperations.SetBinding(p_objNewPane, ContentPane.HeaderProperty, new Binding("ProjectDisplayName") { Source = Project });
p_objNewPane.Closing += new EventHandler<PaneClosingEventArgs>(ContentPane_Closing);
}
if (Project.CalculationExists == true)
{
InitializeCalculation(true);
}
}
tabStartPage.Visibility = Visibility.Collapsed;
objDocumentTabs.SelectedIndex = 0;
}
catch (Exception ex)
{
ModernDialog.ShowMessage(string.Format("An error has occurred:{0}{0}{1}", Environment.NewLine, ex.Message), "Error", MessageBoxButton.OK, Application.Current.MainWindow);
}
finally
{
App.ApplicationBusy = false;
Cursor = Cursors.Arrow;
AppStatus = "Ready";
p_objNewPane = null;
}
}
}
At the start of the try block, I need to update the TextBlock (lblStatus) to say what's going on. The void itself, NewProject, is on the MainWindow, and is called by a button click.
Can someone please give me an idea of where I'm going wrong? I've tried countless potential solutions, so please don't be offended if I get back to you saying I've tried it.
Regards, Tom.
After a few painful days I managed to get this working. I was barking up the wrong tree completely by looking into Task Scheduling, etc. Instead all that was needed was a DependencyProperty.
XAML (Main Window):
<TextBlock x:Name="lblStatus"
Text="{Binding AppStatus, IsAsync=True}"
Grid.Column="0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
FontFamily="Segoe UI"
FontSize="12"
Foreground="White"
Margin="5, 0, 0, 0" />
C# (Main Window):
public string AppStatus
{
get { return (string)GetValue(AppStatusProperty); }
set { SetValue(AppStatusProperty, value); }
}
public static readonly DependencyProperty AppStatusProperty =
DependencyProperty.Register("AppStatus", typeof(string), typeof(MainWindow), new PropertyMetadata(null));
public void StatusBarUpdate(string strMainMessage)
{
Dispatcher.BeginInvoke((Action)(() => { AppStatus = strMainMessage; }));
}
I can then call the StatusBarUpdate method at any time and it will asynchronously update the UI.
You are using WPF, Therefore implement
INotifyPropertyChanged
and use proper data binding.
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public string StatusText
{
get {return this.m_statusText;}
set
{
if (value != this.m_statusText)
{
this.m_statusText= value;
NotifyPropertyChanged("StatusText");
}
}
}
And in XAML
<TextBox Text="{Binding Path=StatusText}"/>
You haven't mentioned what exactly is the problem, but looking at the code I can guess that the textbox shows "Opening Project..." only after completion of your code or shows "Ready" if AppStatus is doing the same thing.
If yes, the problem is that you are doing everything on the UI thread. Although you changed the text of the textbox, it will be rendered after your work is done, hence the problem. To fix this, you need to run the code from if (blnCopy == false) to objDocumentTabs.SelectedIndex = 0; on a worker thread. That will fix your problem.
You can use TPL for that and can also you .ContinueWith with TaskScheduler.FromCurrentSynchronizationContext and TaskContinuationOptions.OnlyOnRanToCompletion to execute the finally block.
EDIT:
As you are not using MVVM, you will need a lot of Invoke to make your code work. As I can see p_objNewPane is also an UI element just like Project is a view property, it will be difficult to translate all that to TPL. You can leave code from p_objNewPane = ... as it is (i.e. outside worker thread). That doesn't seem to be very cpu intensive except maybe InitializeCalculation which you can run in another worker thread.
A better approach is that you use await/async methods to wait for all heavy lifting methods.

listView removing rows and updating

I have a listView that is being updated from multiple threads, with the threads status and id being displayed. When a thread has ended the item is removed.
My problem is that the way i use to update my listView is by using the listViews ID (to locate the subitems i wish to update). Now, as listView items start getting removed i run into problems because that ID for a specific thread has changed..
Here is the code i currently use to update and remove:
private void AddToListViewThread(string user, string status, int threadNumber)
{
Invoke(new MethodInvoker(
delegate
{
listView2.BeginUpdate();
this.listView2.Items[threadNumber].SubItems[1].Text = user;
this.listView2.Items[threadNumber].SubItems[2].Text = status;
listView2.EndUpdate();
}
));
}
private void RemoveFromListViewThread(int threadNumber)
{
Invoke(new
MethodInvoker(
delegate
{
listView2.BeginUpdate();
this.listView2.Items.RemoveAt(threadNumber);
listView2.EndUpdate();
}
));
}
I now understand that i cannot use the threadNumber as the item index (as items get removed) is there any other way i could achieve this? maybe by targeting the "user" subitem? and then getting subitems from that?
Use the tag property of the ListViewItem to store a different index (similar to the SQL autoincrement ).
The Tag property is a field used to store metadata about the item.
A simple binary search would let you find the item to remove in Log(n) time.
Your code would looks like this:
private void AddToListViewThread(string user, string status, int threadNumber)
{
Invoke(new MethodInvoker(
delegate
{
listView2.BeginUpdate();
int i = SearchItem(listView2, threadNumber);
if ( i > -1)
{
this.listView2.Items[i].SubItems[1].Text = user;
this.listView2.Items[i].SubItems[2].Text = status;
}
listView2.EndUpdate();
}
));
}
private void RemoveFromListViewThread(int threadNumber)
{
Invoke(new MethodInvoker(
delegate
{
listView2.BeginUpdate();
int i = SearchItem(listView2, threadNumber);
if ( i > -1)
{
this.listView2.Items.RemoveAt(i);
}
listView2.EndUpdate();
}
));
}
private int SearchItem(ListView list, int id)
{
for (int i = 0; i < list.Items.Count; i++) // I used sequential search but you can implement binary instead
{
if (((int)list.Items[i].Tag) == id) return i;
}
return -1;
}
You may want to look at ListViewItem.Tag to see if it works in your specific scenario. You can store the thread's ID in the item's tag at creation and it won't change if you remove other items in the ListView.
http://msdn.microsoft.com/en-us/library/system.windows.forms.listviewitem.tag.aspx
Try this -> this.listView2.Remove(this.listView2.Items.FindByValue(threadNumber));

Categories