How to get current progress when printing using PrintDialog? - c#

I want to keep showing the currently ongoing Page out of the entire page on the UI thread.
However, no event can be referenced.
I've seen these links:
WPF - How to update ProgressBar based on the progress of a method from a different class?
https://learn.microsoft.com/en-us/answers/questions/1165557/wpf-download-file-with-progress-bar
I have the following code now, but I don't know how to do it.
Is there any better way?
<StackPanel Orientation="Horizontal" >
<Button HorizontalAlignment="Center" Width="80" Click="PrintButtonClick">print</Button>
<ProgressBar x:Name="progressbar" Margin="10,0" Maximum="100" Width="140" Height="20" Value="{Binding Progress}"/>
<TextBox IsReadOnly="True" Width="50" Text="{Binding ProgressValue}"/>
</StackPanel>
<DocumentViewer Name="viewer" Grid.Row="1">
<FixedDocument x:Name="fd">
</FixedDocument>
</DocumentViewer>
</StackPanel>
using System;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Xps.Packaging;
using Window = System.Windows.Window;
using System.Windows.Markup;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using PrintDialog = System.Windows.Controls.PrintDialog;
namespace DocumentViewerPrintProgressbar
{
public partial class MainWindow : Window, ICommand, INotifyPropertyChanged
{
TextBlock page1Text = new TextBlock();
public MainWindow()
{
InitializeComponent();
PrintDialog pd = new PrintDialog();
fd.DocumentPaginator.PageSize = new Size(pd.PrintableAreaWidth, pd.PrintableAreaHeight);
for (int i = 0; i <= 5; i++)
{
FixedPage page1 = new FixedPage();
page1.Width = fd.DocumentPaginator.PageSize.Width;
page1.Height = fd.DocumentPaginator.PageSize.Height;
UIElement page1Text = pages();
page1.Children.Add(page1Text);
PageContent page1Content = new PageContent();
((IAddChild)page1Content).AddChild(page1);
fd.Pages.Add(page1Content);
}
}
private UIElement pages()
{
Canvas pcan = new Canvas();
TextBlock page1Text = new TextBlock();
page1Text.TextWrapping = TextWrapping.Wrap;
for (int i = 1; i < 1200; i++)
{
page1Text.Text += i.ToString() + "This is a testssssssssssssssssssssssssssssssssssssssssssss";
}
page1Text.FontSize = 40;
page1Text.Margin = new Thickness(96);
pcan.Children.Add(page1Text);
return pcan;
}
void PrintButtonClick(object sender, RoutedEventArgs e)
{
var dlg = new PrintDialog();
// Allow the user to select a PageRange
dlg.UserPageRangeEnabled = true;
if (dlg.ShowDialog() == true)
{
DocumentPaginator paginator = fd.DocumentPaginator;
dlg.PrintDocument(paginator, "Just a test");
}
}
public string ProgressValue { get; set; }
public int Status { get; set; }
public double Progress { get; set; }
public void Execute(object parameter)
{
Progress = 0;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
public static bool PrintWholeDocument(string xpsFilePath, bool hidePrintDialog = false)
{
PrintDialog printDialog = new PrintDialog();
if (!hidePrintDialog)
{
bool? isPrinted = printDialog.ShowDialog();
if (isPrinted != true)
return false;
}
try
{
XpsDocument xpsDocument = new XpsDocument(xpsFilePath, FileAccess.Read);
FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();
DocumentPaginator docPaginator = fixedDocSeq.DocumentPaginator;
printDialog.PrintDocument(docPaginator, $"Printing {System.IO.Path.GetFileName(xpsFilePath)}");
return true;
}
catch (Exception e)
{
System.Windows.MessageBox.Show(e.Message);
return false;
}
}
}
}
Update:
private void btnPrint_Click(object sender, RoutedEventArgs e)
{
this.Topmost = false;
PrintDialog printDialog = new PrintDialog();
CancellationTokenSource printServerWatchTaskCancellationTokenSource = new CancellationTokenSource();
PrintServer printServer = new PrintServer(PrintSystemDesiredAccess.AdministrateServer);
if (printDialog.ShowDialog() == true)
{
Task printServerTask = new Task(async () =>
{
await this.Dispatcher.InvokeAsync(async () =>
{
PrintQueue _queue = new PrintQueue(printServer, printDialog.PrintQueue.FullName);
StringBuilder sb = new StringBuilder();
PrintSystemJobInfo job;
while (true)
{
_queue.Refresh();
if (_queue.NumberOfJobs > 0)
{
sb.Clear();
job = _queue.GetPrintJobInfoCollection().Where(x => x.Name.Equals("AbleSoft PostDocument2.0")).SingleOrDefault();
if (job != null)
{
switch (job.JobStatus)
{
case PrintJobStatus.Spooling:
sb.AppendLine($"Spooling");
sb.AppendLine($"{job.NumberOfPages} / {PrintTargetFileInfo.Count}");
break;
case PrintJobStatus.Printing:
case (PrintJobStatus.Printing | PrintJobStatus.Retained):
sb.AppendLine($"Printing");
sb.AppendLine($"{job.NumberOfPagesPrinted} / {PrintTargetFileInfo.Count}");
break;
}
ProgressText.Dispatcher.Invoke(() =>
{
ProgressText.Text = sb.ToString();
});
}
}
await Task.Delay(1);
}
}, System.Windows.Threading.DispatcherPriority.Background);
await Task.Delay(1);
}, printServerWatchTaskCancellationTokenSource.Token);
printServerTask.Start();
printDialog.PrintDocument(documentViewer.Document.DocumentPaginator, "AbleSoft PostDocument2.0");
printServerWatchTaskCancellationTokenSource.Cancel();
}
}

The PrintDialog is not using any async API. So printing is synchronous. This means you can't update the UI in realtime.
As a solution you would have to print the document manually. This allows to use asynchronous APIs. In addition, you get more control over the process (for example you can pick a printer or configure the print job explicitly).
To allow the user to pick a printer, you would have to create your own custom small dialog.
A custom DocumentPaginator implementation (RangeDocumentPaginator) is used to support printing page ranges.
MainWindow.xaml.cs
private CancellationTokenSource CancellationTokenSource { get; set; }
private async void OnPrintButtonClickedAsync(object sender, EventArgs e)
{
DocumentPaginator documentPaginator = ((IDocumentPaginatorSource)this.Document).DocumentPaginator;
var progressReporter = new Progress<(int PageNumber, int Percentage)>(ReportProgress);
using var printer = new DocumentPrinter();
this.ProgressBar.Maximum = await printer.GetPageCountAsync(documentPaginator);
this.CancellationTokenSource = new CancellationTokenSource();
try
{
// Print complete document
await printer.PrintFullDocumentAsync(documentPaginator, progressReporter, this.CancellationTokenSource.Token);
// Print document pages 1 to 10.
// The page numbers must be converted to indices 0 to 9.
// The method takes a 'Range' as argument.
// 'Range' is defined as inclusive start index and exclusive end index.
// Therefore the 'Range' for the pages 1 to 10 is '0..10'.
await printer.PrintDocumentPageRangeAsync(documentPaginator, 0..10, progressReporter, this.CancellationTokenSource.Token);
}
catch (OperationCanceledException)
{
}
finally
{
this.CancellationTokenSource.Dispose();
this.CancellationTokenSource = null;
}
}
private void OnCancelButtonClicked(object sender, RoutedEventArgs e)
=> this.CancellationTokenSource?.Cancel();
private void ReportProgress((int PageCount, int Percentage) progress)
=> this.ProgressBar.Value = progress.PageCount;
DocumentPrinter.cs
public class DocumentPrinter : IDisposable
{
private TaskCompletionSource PrintTaskCompletionSource { get; set; }
private TaskCompletionSource<int> ComputePagesTaskCompletionSource { get; set; }
private PrintServer PrintServer { get; }
private IProgress<(int PageNumber, int Percentage)> CurrentProgressReporter { get; set; }
private bool IsPrintJobPending { get; set; }
private CancellationToken CancellationToken { get; set; }
public DocumentPrinter() => this.PrintServer = new LocalPrintServer();
public Task<int> GetPageCountAsync(DocumentPaginator documentPaginator)
{
this.CancellationToken = CancellationToken.None;
if (!documentPaginator.IsPageCountValid)
{
this.ComputePagesTaskCompletionSource = new TaskCompletionSource<int>(TaskCreationOptions.None);
documentPaginator.ComputePageCountCompleted += OnCountingPagesCompleted;
documentPaginator.ComputePageCountAsync();
return this.ComputePagesTaskCompletionSource.Task;
}
return Task.FromResult(documentPaginator.PageCount);
}
public async Task PrintFullDocumentAsync(DocumentPaginator documentPaginator, IProgress<(int PageNumber, int Percentage)> progressReporter, CancellationToken cancellationToken)
=> await PrintDocumentPageRangeAsync(documentPaginator, .., progressReporter, cancellationToken);
public Task PrintDocumentPageRangeAsync(DocumentPaginator documentPaginator, Range pageIndexRange, IProgress<(int PageNumber, int Percentage)> progressReporter, CancellationToken cancellationToke)
{
this.CancellationToken = cancellationToken;
this.CancellationToken.ThrowIfCancellationRequested();
this.IsPrintJobPending = true;
this.CurrentProgressReporter = progressReporter;
this.PrintTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.None);
var rangeDocumentPaginator = new RangeDocumentPaginator(documentPaginator, pageIndexRange);
if (!rangeDocumentPaginator.IsPageCountValid)
{
this.ComputePagesTaskCompletionSource = new TaskCompletionSource<int>(TaskCreationOptions.None);
rangeDocumentPaginator.ComputePageCountCompleted += OnCountingPagesCompleted;
rangeDocumentPaginator.ComputePageCountAsync();
return this.PrintTaskCompletionSource.Task;
}
StartPrintJob(rangeDocumentPaginator);
this.IsPrintJobPending = false;
return this.PrintTaskCompletionSource.Task;
}
private void StartPrintJob(DocumentPaginator documentPaginator)
{
this.CancellationToken.ThrowIfCancellationRequested();
/* Select the destination printer */
// Optionally show a custom printer picker dialog
PrintQueue destinationPrinter = GetPrintQueueFromUser(printServer);
// Alternatively use the default printer
PrintQueue destinationPrinter = printServer.DefaultPrintQueue;
// Alternatively, pick a particular printer explicitly e.g., native PDF printer
PrintQueue destinationPrinter = this.PrintServer.GetPrintQueue("Microsoft Print to PDF");
/* Start the printing */
// Create a XpsDocumentWriter that writes to the printer queue
XpsDocumentWriter documentWriter = PrintQueue.CreateXpsDocumentWriter(destinationPrinter);
documentWriter.WritingProgressChanged += OnPrintProgressChanged;
documentWriter.WritingCompleted += OnPrintingCompleted;
documentWriter.WriteAsync(documentPaginator);
}
private PrintQueue GetPrintQueueFromUser(PrintServer printServer)
{
PrintQueueCollection printers = printServer.GetPrintQueues();
// TODO::Implement MyPrinterPickerDialog (extend Window)
var myPrinterPickerDialog = new MyPrinterPickerDialog(printers);
myPrinterPickerDialog.ShowDialog();
return myPrinterPickerDialog.SelectedPrintQueue;
}
private void OnCountingPagesCompleted(object sender,
System.ComponentModel.AsyncCompletedEventArgs e)
{
var documentPaginator = sender as DocumentPaginator;
documentPaginator.ComputePageCountCompleted -= OnCountingPagesCompleted;
if (this.CancellationToken.IsCancellationRequested)
{
this.ComputePagesTaskCompletionSource.TrySetCanceled(this.CancellationToken);
this.CancellationToken.ThrowIfCancellationRequested();
}
else
{
_ = this.ComputePagesTaskCompletionSource.TrySetResult(documentPaginator.PageCount);
}
if (this.IsPrintJobPending)
{
StartPrintJob(documentPaginator);
}
}
private void OnPrintProgressChanged(object sender, WritingProgressChangedEventArgs e)
{
var documentPrinter = sender as XpsDocumentWriter;
this.CurrentProgressReporter.Report((e.Number, e.ProgressPercentage));
if (this.CancellationToken.IsCancellationRequested)
{
documentPrinter.WritingCancelled += OnPrintingCancelled;
documentPrinter.CancelAsync();
}
}
private void OnPrintingCancelled(object sender, WritingCancelledEventArgs e)
{
var documentPrinter = sender as XpsDocumentWriter;
documentPrinter.WritingCancelled -= OnPrintingCancelled;
this.PrintTaskCompletionSource.TrySetCanceled(this.CancellationToken);
this.CancellationToken.ThrowIfCancellationRequested();
}
private void OnPrintingCompleted(object sender, WritingCompletedEventArgs e)
{
// TODO::Handle errors by checking event args
documentWriter.WritingCompleted -= OnPrintingCompleted;
documentWriter.WritingProgressChanged -= OnPrintProgressChanged;
_ = this.PrintTaskCompletionSource.TrySetResult();
}
private bool disposedValue;
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
this.PrintServer.Dispose();
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
this.disposedValue = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~DocumentPrinter()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
RangeDocumentPaginator.cs
internal class RangeDocumentPaginator : DocumentPaginator
{
protected RangeDocumentPaginator() : base() { }
public RangeDocumentPaginator(DocumentPaginator documentPaginator, Range pageNumberRange)
{
this.DocumentPaginator = documentPaginator;
this.PageIndexRange = pageNumberRange;
if (!this.DocumentPaginator.IsPageCountValid)
{
this.DocumentPaginator.ComputePageCountCompleted += OnPageCountCompleted;
this.DocumentPaginator.ComputePageCountAsync();
}
else
{
ThrowIfPageRangeIsInvalid();
}
}
private void ThrowIfPageRangeIsInvalid()
{
if (this.PageIndexRange.Start.Value < 0
|| this.PageIndexRange.End.Value > this.DocumentPaginator.PageCount)
{
throw new IndexOutOfRangeException();
}
}
private void OnPageCountCompleted(object sender, AsyncCompletedEventArgs e)
=> ThrowIfPageRangeIsInvalid();
public override DocumentPage GetPage(int pageIndex)
{
// XpsDocumentWriter will always start with page index 0.
// We have to use the start page index as offset
// to return the correct pages of the range from the underlying document.
// XpsDocumentWriter will use the PageCount property to know how many pages it can fetch.
// We have manipulated the PageCount property to return the count of the pages contained within the range.
int pageOffset = this.PageIndexRange.Start.Value;
pageIndex += pageOffset;
return pageIndex < this.PageIndexRange.Start.Value
|| (pageIndex >= this.PageIndexRange.End.Value && !this.IsRangeFullDocument)
? DocumentPage.Missing
: this.DocumentPaginator.GetPage(pageIndex);
}
public override bool IsPageCountValid => this.DocumentPaginator.IsPageCountValid;
public override int PageCount
{
get
{
int range = this.PageIndexRange.End.Value - this.PageIndexRange.Start.Value;
return this.IsRangeFullDocument
? this.DocumentPaginator.PageCount
: range;
}
}
public override Size PageSize { get => this.DocumentPaginator.PageSize; set => this.DocumentPaginator.PageSize = value; }
public override IDocumentPaginatorSource Source => this.DocumentPaginator.Source;
public Range PageIndexRange { get; set; }
public bool IsRangeFullDocument => this.PageIndexRange.Equals(Range.All);
private DocumentPaginator DocumentPaginator { get; }
}

Related

Multi UI-threading and databinding issues

I'm having issues updating the UI threads. Application is running 1 UI thread for each form, meaning just using SyncronizationContext with the UI thread doesn't work. I'm doing this for looping update performance as well as modal popup possibilities like select value before you can use the form.
How I'm creating it in ApplicationContext:
public AppContext()
{
foreach(var form in activeForms)
{
form.Load += Form_Load;
form.FormClosed += Form_FormClosed;
StartFormInSeparateThread(form);
//form.Show();
}
}
private void StartFormInSeparateThread(Form form)
{
Thread thread = new Thread(() =>
{
Application.Run(form);
});
thread.ApartmentState = ApartmentState.STA;
thread.Start();
}
There are controls on each for that are databound and updating with values from the same databound object. Controls being Labels and DataGridview (bound to a bindinglist).
What would be ideal is having the Bindinglist threadsafe and execute on these multiple UI threads. Found some examples that I attempted like this:
List<SynchronizationContext> listctx = new();
public ThreadSafeBindingList2()
{
//SynchronizationContext ctx = SynchronizationContext.Current;
//listctx.Add(ctx);
}
public void SyncContxt()
{
SynchronizationContext ctx = SynchronizationContext.Current;
listctx.Add(ctx);
}
protected override void OnAddingNew(AddingNewEventArgs e)
{
for (int i = 0; i < listctx.Count; i++)
{
if (listctx[i] == null)
{
BaseAddingNew(e);
}
else
{
listctx[i].Send(delegate
{
BaseAddingNew(e);
}, null);
}
}
}
void BaseAddingNew(AddingNewEventArgs e)
{
base.OnAddingNew(e);
}
protected override void OnListChanged(ListChangedEventArgs e)
{
for (int i = 0; i < listctx.Count; i++)
{
if (listctx[i] == null)
{
BaseListChanged(e);
}
else
{
listctx[i].Send(delegate
{
BaseListChanged(e);
}, null);
}
}
}
void BaseListChanged(ListChangedEventArgs e)
{
base.OnListChanged(e);
}
I'm also using a static class as a data property change hub for all controls so I don't change the databinding source more than once (again due to performance), where I have a background worker "ticking" every 1-3 seconds depending on system load:
private static void BackgroundWorker_DoWork(object? sender, DoWorkEventArgs e)
{
if (timerStart is false)
{
Thread.Sleep(6000);
timerStart = true;
}
while (DisplayTimerUpdateBGW.CancellationPending is false)
{
//UIThread.Post((object stat) => //Send
//{
threadSleepTimer = OrderList.Where(x => x.Status != OrderOrderlineStatus.Claimed).ToList().Count > 20 ? 2000 : 1000;
if (OrderList.Count > 40)
threadSleepTimer = 3000;
UpdateDisplayTimer();
//}, null);
Thread.Sleep(threadSleepTimer);
}
}
private static void UpdateDisplayTimer()
{
var displayLoopStartTimer = DateTime.Now;
TimeSpan displayLoopEndTimer = new();
Span<int> orderID = CollectionsMarshal.AsSpan(OrderList.Select(x => x.ID).ToList());
for (int i = 0; i < orderID.Length; i++)
{
OrderModel order = OrderList[i];
order.OrderInfo = "Ble";
Span<int> OrderLineID = CollectionsMarshal.AsSpan(order.Orderlines.Select(x => x.Id).ToList());
for (int j = 0; j < OrderLineID.Length; j++)
{
OrderlineModel ol = order.Orderlines[j];
TimeSpan TotalElapsedTime = ol.OrderlineCompletedTimeStamp != null ? (TimeSpan)(ol.OrderlineCompletedTimeStamp - ol.OrderlineReceivedTimeStamp) : DateTime.Now - ol.OrderlineReceivedTimeStamp;
string displaytimerValue = "";
if (ol.OrderlineCompletedTimeStamp == null)
displaytimerValue = TotalElapsedTime.ToString(#"mm\:ss");
else
displaytimerValue = $" {(DateTime.Now - ol.OrderlineCompletedTimeStamp)?.ToString(#"mm\:ss")} \n({TotalElapsedTime.ToString(#"mm\:ss")})";
ol.DisplayTimer = displaytimerValue;
}
}
}
Ideally I want to have the labels and datagridview properties databindings so that I can have INotifyPropertyChanged just updating these relevant properties in all UI threads.
Any help would be appreciated!
One of many ways to look at this is that there's only one display area (albeit which might consist of many screens) and only one element of it can change at any given moment. To my way of thinking, this means that having more than one UI thread can often be self defeating (unless your UI is testing another UI). And since the machine has some finite number of cores, having a very large number of threads (whether of the UI or worker variety) means you can start to have a lot of overhead marshalling the context as threads switch off.
If we wanted to make a Minimal Reproducible Example that has 10 Form objects executing continuous "mock update" tasks in parallel, what we could do instead of the "data property change hub" you mentioned is to implement INotifyPropertyChanged in those form classes with static PropertyChanged event that gets fired when the update occurs. To mock data binding where FormWithLongRunningTask is the binding source, the main form subscribes to the PropertyChanged event and adds a new Record to the BindingList<Record> by identifying the sender and inspecting e to determine which property has changed. In this case, if the property is TimeStamp, the received data is marshalled onto the one-and-only UI thread to display the result in the DataGridView.
public partial class MainForm : Form
{
public MainForm() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// Subscribe to the static event here.
FormWithLongRunningTask.PropertyChanged += onAnyFWLRTPropertyChanged;
// Start up the 10 forms which will do "popcorn" updates.
for (int i = 0; i < 10; i++)
{
new FormWithLongRunningTask { Name = $"Form{i}" }.Show(this);
}
}
private void onAnyFWLRTPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (sender is FormWithLongRunningTask form)
{
BeginInvoke(() =>
{
switch (e.PropertyName)
{
case nameof(FormWithLongRunningTask.TimeStamp):
dataGridViewEx.DataSource.Add(new Record
{
Sender = form.Name,
TimeStamp = form.TimeStamp,
});
break;
default:
break;
}
});
}
}
}
The DataGridView on the main form uses this custom class:
class DataGridViewEx : DataGridView
{
public new BindingList<Record> DataSource { get; } = new BindingList<Record>();
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
if (!DesignMode)
{
base.DataSource = this.DataSource;
AllowUserToAddRows = false;
#region F O R M A T C O L U M N S
DataSource.Add(new Record());
Columns[nameof(Record.Sender)].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
var col = Columns[nameof(Record.TimeStamp)];
col.AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells;
col.DefaultCellStyle.Format = "hh:mm:ss tt";
DataSource.Clear();
#endregion F O R M A T C O L U M N S
}
}
protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
{
base.OnCellPainting(e);
if ((e.RowIndex > -1) && (e.RowIndex < DataSource.Count))
{
var record = DataSource[e.RowIndex];
var color = _colors[int.Parse(record.Sender.Replace("Form", string.Empty))];
e.CellStyle.ForeColor = color;
if (e.ColumnIndex > 0)
{
CurrentCell = this[0, e.RowIndex];
}
}
}
Color[] _colors = new Color[]
{
Color.Black, Color.Blue, Color.Green, Color.LightSalmon, Color.SeaGreen,
Color.BlueViolet, Color.DarkCyan, Color.Maroon, Color.Chocolate, Color.DarkKhaki
};
}
class Record
{
public string Sender { get; set; } = string.Empty;
public DateTime TimeStamp { get; set; }
}
The 'other' 10 forms use this class which mocks a binding source like this:
public partial class FormWithLongRunningTask : Form, INotifyPropertyChanged
{
static Random _rando = new Random(8);
public FormWithLongRunningTask() => InitializeComponent();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
_ = runRandomDelayLoop();
}
private async Task runRandomDelayLoop()
{
while(true)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(_rando.NextDouble() * 10));
TimeStamp = DateTime.Now;
Text = $"# {TimeStamp.ToLongTimeString()}";
BringToFront();
}
catch (ObjectDisposedException)
{
}
}
}
DateTime _timeStamp = DateTime.Now;
public DateTime TimeStamp
{
get => _timeStamp;
set
{
if (!Equals(_timeStamp, value))
{
_timeStamp = value;
OnPropertyChanged();
}
}
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
event PropertyChangedEventHandler? INotifyPropertyChanged.PropertyChanged
{
add => PropertyChanged += value;
remove => PropertyChanged -= value;
}
public static event PropertyChangedEventHandler? PropertyChanged;
}
I believe that there's no 'right' answer to your question but I hope there's something here that might move things forward for you.

Xamarin forms Combobox:SFComboBox!! How i can refresh my combobox?

I'm trying to use CollectionChanged.
My code:
public List<RSSNotification> Notificacoes { get; private set; }
public RSSReader rssreader;
public List<RSSReader> feedsAdicionados = new List<RSSReader>();
ObservableCollection<string> comboFeeds = new ObservableCollection<string>();
public ThirdPage(List<RSSReader> feed, List<RSSNotification> Not)
{
InitializeComponent();
mycombobox.ItemsSource = ComboFeeds;
ComboFeeds.Clear();
Notificacoes = Not;
feedsAdicionados = feed;
for (int i = 0; i < feedsAdicionados.Count; i++)
{
if (feedsAdicionados[i] != null) ComboFeeds.Add(feedsAdicionados[i].title);
}
}
private async void ButtonAdicionar_Clicked(object sender, EventArgs e)
{
rssreader = new RSSReader();
PopUp1 Pop = new PopUp1(0, rssreader, feedsAdicionados, Notificacoes);
await Navigation.ShowPopupAsync(Pop);
await DisplayAlert("Sucesso", "Feed Adicionado com sucesso", "Ok" );
Notificacoes = Pop.Notificacoes;
rssreader = Pop.RSS;
ComboFeeds.Add(rssreader.title);
this.ComboFeeds.CollectionChanged += ComboFeeds_CollectionChanged;
}
private void ComboFeeds_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
}
When the popup ends, I add it to my combobox via a class, then try to update, but it doesn't work.
Thx for all help

Set tooltip on range of text in wpf richtextbox

I am trying to set a tooltip on an arbitrary range of text on a richtextbox. Is this possible? If so how would I do it e.g by passing in parameters "from" and "to" as (int) indexes.
Thanks
You could use the following as a starting point:
Add a reference to System.Windows.Interactivity.
Add the following classes to your project:
public class TextRangeToolTip
{
public int StartPosition { get; set; }
public int Length { get; set; }
public object ToolTip { get; set; }
internal bool IsInRange(int position)
{
return this.StartPosition <= position && position < this.StartPosition + this.Length;
}
}
public class TextRangeToolTipCollection : ObservableCollection<TextRangeToolTip> {}
[ContentProperty("Ranges")]
public class ToolTipBehavior : Behavior<RichTextBox>
{
private const int ToolTipHideMouseDelta = 9;
public static readonly DependencyProperty RangesProperty
= DependencyProperty.Register("Ranges", typeof(TextRangeToolTipCollection),
typeof (ToolTipBehavior),
new PropertyMetadata(OnRangesChanged));
private readonly DispatcherTimer timer;
private readonly ToolTip toolTip;
private Point lastMousePosition;
public TextRangeToolTipCollection Ranges
{
get
{
return (TextRangeToolTipCollection)this.GetValue(RangesProperty)
?? (this.Ranges = new TextRangeToolTipCollection());
}
set { this.SetValue(RangesProperty, value); }
}
public ToolTipBehavior()
{
this.Ranges = new TextRangeToolTipCollection();
this.timer = new DispatcherTimer();
this.timer.Tick += this.TimerOnTick;
this.timer.Interval = TimeSpan.FromSeconds(1);
this.toolTip = new ToolTip {Placement = PlacementMode.Relative};
}
protected override void OnAttached()
{
this.AssociatedObject.ToolTip = this.toolTip;
this.toolTip.PlacementTarget = this.AssociatedObject;
ToolTipService.SetIsEnabled(this.AssociatedObject, false);
this.AssociatedObject.MouseMove += this.AssociatedObjectOnMouseMove;
}
protected override void OnDetaching()
{
this.timer.Stop();
this.toolTip.PlacementTarget = null;
this.AssociatedObject.ToolTip = null;
this.AssociatedObject.ClearValue(ToolTipService.IsEnabledProperty);
this.AssociatedObject.MouseMove -= this.AssociatedObjectOnMouseMove;
}
private void AssociatedObjectOnMouseMove(object sender, MouseEventArgs mouseEventArgs)
{
Point currentMousePosition = mouseEventArgs.GetPosition(this.AssociatedObject);
if (this.AssociatedObject.IsMouseCaptured)
{
Vector delta = currentMousePosition
- this.lastMousePosition;
if (delta.X*delta.X + delta.Y*delta.Y <= ToolTipHideMouseDelta)
{
this.toolTip.HorizontalOffset = currentMousePosition.X + 10;
this.toolTip.VerticalOffset = currentMousePosition.Y + 10;
return;
}
this.AssociatedObject.ReleaseMouseCapture();
this.toolTip.IsOpen = false;
}
if (this.AssociatedObject.IsMouseOver)
{
this.lastMousePosition = currentMousePosition;
this.timer.Stop();
this.timer.Start();
}
}
private static void OnRangesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ToolTipBehavior) d).OnRangesChanged((IEnumerable<TextRangeToolTip>) e.OldValue,
(IEnumerable<TextRangeToolTip>) e.NewValue);
}
private void OnRangesChanged(IEnumerable<TextRangeToolTip> oldRanges, IEnumerable<TextRangeToolTip> newRanges)
{
var oldObservable = oldRanges as INotifyCollectionChanged;
if (oldObservable != null)
{
CollectionChangedEventManager.RemoveHandler(oldObservable, this.OnRangesCollectionChanged);
}
var newObservable = newRanges as INotifyCollectionChanged;
if (newObservable != null)
{
CollectionChangedEventManager.AddHandler(newObservable, this.OnRangesCollectionChanged);
}
this.UpdateToolTip();
}
private void OnRangesCollectionChanged(
object sender,
NotifyCollectionChangedEventArgs notifyCollectionChangedEventArgs)
{
this.UpdateToolTip();
}
private bool SetToolTipData()
{
if (this.Ranges == null)
{
return false;
}
TextPointer pointer = this.AssociatedObject.GetPositionFromPoint(this.lastMousePosition, false);
if (pointer == null)
{
return false;
}
int position = this.AssociatedObject.Document.ContentStart.GetOffsetToPosition(pointer);
TextRangeToolTip matchingRange = this.Ranges.FirstOrDefault(r => r.IsInRange(position));
if (matchingRange == null)
{
return false;
}
this.toolTip.Content = matchingRange.ToolTip;
return true;
}
private void TimerOnTick(object sender, EventArgs eventArgs)
{
this.timer.Stop();
if (this.AssociatedObject.IsMouseOver && this.SetToolTipData())
{
this.toolTip.IsOpen = true;
this.AssociatedObject.CaptureMouse();
}
}
private void UpdateToolTip()
{
if (this.AssociatedObject != null && this.AssociatedObject.IsMouseCaptured && !this.SetToolTipData())
{
this.toolTip.IsOpen = false;
this.AssociatedObject.ReleaseMouseCapture();
}
}
}
Use it on your RichTextBox like this:
<RichTextBox>
<i:Interaction.Behaviors>
<myapp:ToolTipBehavior>
<myapp:TextRangeToolTip StartPosition="10" Length="4" ToolTip="some" />
<myapp:TextRangeToolTip StartPosition="15" Length="4" ToolTip="text" />
</myapp:ToolTipBehavior>
</i:Interaction.Behaviors>
<FlowDocument>
<Paragraph>This is some text. This is some other text.</Paragraph>
</FlowDocument>
</RichTextBox>
Alternatively, you can bind a TextRangeToolTipCollection to the Ranges property like this:
<RichTextBox Document="{Binding Document}">
<i:Interaction.Behaviors>
<myapp:ToolTipBehavior Ranges="{Binding RangeToolTips}" />
</i:Interaction.Behaviors>
</RichTextBox>
Getting the positions right is a bit tricky, because WPF counts symbols, not characters. You could extend the TextRangeToolTip class to have properties of type TextPointer or TextRange and construct it using your FlowDocument instance.

How to get latitude and longitude in Mono for android?

First i worked with this tutorial tutorial
to get latitude and longitude, but i get nothing, so this is my code :
[Activity(Label = "GetLocation", MainLauncher = true, Icon = "#drawable/icon")]
public class Activity1 : Activity, ILocationListener
{
private Location _currentLocation;
private LocationManager _locationManager;
private TextView _locationText;
private TextView _addressText;
private string _locationProvider;
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
_addressText = FindViewById<TextView>(Resource.Id.address_text);
_locationText = FindViewById<TextView>(Resource.Id.location_text);
FindViewById<TextView>(Resource.Id.get_address_button).Click += AddressButton_OnClick;
InitializeLocationManager();
}
private void InitializeLocationManager()
{
_locationManager = (LocationManager)GetSystemService(LocationService);
var criteriaForLocationService = new Criteria
{
Accuracy = Accuracy.Fine
};
var acceptableLocationProviders = _locationManager.GetProviders(criteriaForLocationService, true);
if (acceptableLocationProviders.Any())
{
_locationProvider = acceptableLocationProviders.First();
}
else
{
_locationProvider = String.Empty;
}
}
protected override void OnResume()
{
base.OnResume();
_locationManager.RequestLocationUpdates(_locationProvider, 0, 0, this);
}
protected override void OnPause()
{
base.OnPause();
_locationManager.RemoveUpdates(this);
}
private void AddressButton_OnClick(object sender, EventArgs eventArgs)
{
if (_currentLocation == null)
{
_addressText.Text = "Can't determine the current location.";
return;
}
new Thread(() =>
{
var addressText = "Unable to find a location.";
var geocoder = new Geocoder(this);
var addressList = geocoder.GetFromLocation(_currentLocation.Latitude, _currentLocation.Longitude, 50);
var address = addressList.FirstOrDefault();
if (address != null)
{
var deviceLocation = new StringBuilder();
for (var i = 0; i < address.MaxAddressLineIndex; i++)
{
deviceLocation.Append(address.GetAddressLine(i))
.AppendLine(",");
}
_addressText.Text = deviceLocation.ToString();
}
RunOnUiThread(() => { _addressText.Text = addressText; });
}).Start();
}
public void OnLocationChanged(Location location)
{
_currentLocation = location;
if (_currentLocation == null)
{
_locationText.Text = "Unable to determine your location.";
}
else
{
_locationText.Text = String.Format("{0},{1}", _currentLocation.Latitude, _currentLocation.Longitude);
}
}
public void OnProviderDisabled(string provider) { }
public void OnProviderEnabled(string provider) { }
public void OnStatusChanged(string provider, Availability status, Bundle extras) { }
}
So please if someone has any idea about what wrong with my code i will be very appreciative.
There is one spot in your code where you're updating the the UI _addressText.Text from a background thread. That could also account for why, when you click the button, you're not seeing any address updates. See below for a snippet of the one line of code:
if (address != null)
{
var deviceLocation = new StringBuilder();
for (var i = 0; i < address.MaxAddressLineIndex; i++)
{
deviceLocation.Append(address.GetAddressLine(i))
.AppendLine(",");
}
// Here you were updating the UI thread from the background:
RunOnUiThread(() => _addressText.Text = deviceLocation.ToString());
}

How to receive property changed notifications in a Form when a property changes in a class?

I have a class with INotifyPropertyChanged interface. There is a property with the name Total Progress.
I have a Form with Progress Bar on it. I want to send the TotalProgress property changed notifications to this Progress Bar and set it's value.
Do I need to catch the PropertyChangedEvent in the Form also?
Edit: WPF Form Code
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
using System;
using System.Windows.Threading;
namespace SUpdater
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
BackgroundWorker bw = new BackgroundWorker();
DownloadFile FileDownloadClass = new DownloadFile();
public MainWindow()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
progressBar1.SetBinding(System.Windows.Controls.ProgressBar.ValueProperty, new Binding("TotalPercentCompleted"));
progressBar1.DataContext = FileDownloadClass;
FileDownloadClass.PropertyChanged +=new PropertyChangedEventHandler(FileDownloadClass_PropertyChanged);
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
FileDownloadClass.DownloadFiles();
if ((bw.CancellationPending == true))
e.Cancel = true;
else
{
bw.ReportProgress(FileDownloadClass.TotalPercentCompleted);
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.lblConnectionStatus.Content = " Download Canceled!";
}
else if (!(e.Error == null))
{
this.lblConnectionStatus.Content = ("Error: " + e.Error.Message);
}
else
{
this.lblConnectionStatus.Content = "Done!";
}
}
private void FileDownloadClass_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
lblKbCompleted.Content = e.ProgressPercentage.ToString();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
bw.RunWorkerAsync();
}
}
}
Edit: DownloadFile Class Code
sealed class DownloadFile:INotifyPropertyChanged
{
#region Private Fields
// These fields hold the values for the public properties.
private int progressBarValue = 0;
private int totalKbCompleted = 0;
private int totalBytesReceived = 0;
private int remoteFileSize = 0;
private string fileName = String.Empty;
private string statusMessage = String.Empty;
#endregion
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
#region Public Properties
public int TotalKbCompleted
{
get { return this.totalKbCompleted; }
set
{
if (value != this.totalKbCompleted)
{
this.totalKbCompleted = value/1024;
NotifyPropertyChanged("TotalKbCompleted");
}
}
}
public int TotalBytesReceived
{
get { return this.totalBytesReceived; }
set
{
if (value != this.totalBytesReceived)
{
this.totalBytesReceived = value;
NotifyPropertyChanged("TotalBytesReceived");
}
}
}
public int RemoteFileSize
{
get { return this.remoteFileSize; }
set
{
if (value != this.remoteFileSize)
{
this.remoteFileSize = value;
NotifyPropertyChanged("RemoteFileSize");
}
}
}
public string CurrentFileName
{
get { return this.fileName; }
set
{
if (value != this.fileName)
{
this.fileName = value;
NotifyPropertyChanged("CurrentFileName");
}
}
}
public string StatusMessage
{
get { return this.statusMessage; }
set
{
if (value != this.statusMessage)
{
this.statusMessage = value;
NotifyPropertyChanged("StatusMessage");
}
}
}
#endregion
public Int16 DownloadFiles()
{
try
{
statusMessage = "Attempting Connection with Server";
DoEvents();
// create a new ftpclient object with the host and port number to use
FtpClient ftp = new FtpClient("mySite", 21);
// registered an event hook for the transfer complete event so we get an update when the transfer is over
//ftp.TransferComplete += new EventHandler<TransferCompleteEventArgs>(ftp_TransferComplete);
// open a connection to the ftp server with a username and password
statusMessage = "Connected. Authenticating ....";
ftp.Open("User Name", "Password");
// Determine File Size of the compressed file to download
statusMessage = "Getting File Details";
RemoteFileSize = Convert.ToInt32(ftp.GetFileSize("myFile.exe"));
ftp.TransferProgress += new EventHandler<TransferProgressEventArgs>(ftp_TransferProgress);
statusMessage = "Download from Server";
ftp.GetFile("myFile.exe", "E:\\Test\\myFile.exe", FileAction.Create);
// close the ftp connection
ftp.Close();
statusMessage = "Download Complete";
return 1;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
return 0;
}
}
private void ftp_TransferProgress(object sender, TransferProgressEventArgs e)
{
totalBytesReceived = Convert.ToInt32(e.BytesTransferred.ToString());
totalKbCompleted = Convert.ToInt32(totalKbCompleted + Convert.ToInt32(totalBytesReceived));
progressBarValue = totalKbCompleted;
}
}
You can use control binding:
Windows Forms:
progressBar1.DataBindings.Add("Value", dataSource, dataMember, true,
DataSourceUpdateMode.OnPropertyChanged);
where the dataSource is your class. and the dataMember is the property name in that class "TotalProgress".
Edit: For WPF
progressBar1.SetBinding(ProgressBar.ValueProperty, new Binding("ProgressTotal"));
progressBar1.DataContext = the instance of the class you want to bind to its property;
For more information about wpf data binding check this and this.
Edit2: Here is an full example:
Foo _foo = new Foo();
DispatcherTimer _dispatcherTimer = new DispatcherTimer();
public MainWindow()
{
InitializeComponent();
_dispatcherTimer.Interval = TimeSpan.FromSeconds(1);
_dispatcherTimer.Tick += _dispatcherTimer_Tick;
_dispatcherTimer.Start();
progressBar1.SetBinding(ProgressBar.ValueProperty, new Binding("ProgressTotal"));
progressBar1.DataContext = _foo;
}
private void _dispatcherTimer_Tick(object sender, EventArgs e)
{
_foo.ProgressTotal = (_foo.ProgressTotal + 10) % progressBar1.Maximum;
}
public class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private double _progressTotal = 0;
public double ProgressTotal
{
get { return _progressTotal; }
set
{
if (value != _progressTotal)
{
_progressTotal = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("ProgressTotal"));
}
}
}
}
}
Edit: Add a timer to preview the effect.
Edit: After you uploading your code, the problem appears in two positions:
The name of the variable is TotalKbCompleted no TotalPercentCompleted. so change the binding line to:
progressBar1.SetBinding(System.Windows.Controls.ProgressBar.ValueProperty, new Binding("TotalKbCompleted"));
You are updating the totalKbCompleted instead of TotalKbCompleted so the property changed will not trigger.

Categories

Resources