I've looked at a lot of other stack overflow posts but none of them really were similar to what I am trying to accomplish. Basically, I am trying to connect to a device running Windows CE through an SSH connection and capture any output that is printed to the terminal. When I connect via ssh using Putty I can see many print statements in the terminal which are used for debugging. I am trying to capture these debugging statements and use them in my wpf application. These debugging statements are not a response to a command, they are just printed to the terminal.
So far I am able to send a command and receive a single response but what I am looking for is to be able to receive a response indefinitely, until the user closes the connection or the application.
I am using Renci.SshNet to send my commands and I was messing around with using a ShellStream but was not able to get it working. Here is what I have so far:
using System;
using System.Threading;
using System.Windows;
using Renci.SshNet;
namespace TestSshConsole
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private SshClient _sshConnection;
private ShellStream _shellStream;
private delegate void UpdateTextCallback(string message);
public MainWindow()
{
InitializeComponent();
}
/// <summary>
/// When the user presses connect, connect to the device
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Button_Click(object sender, RoutedEventArgs e)
{
try
{
// Connect to device
_sshConnection = new SshClient(hostname.Text, int.Parse(port.Text), username.Text, password.Text);
_sshConnection.Connect();
// Create a shell stream
_shellStream = _sshConnection.CreateShellStream("test", 80, 60, 800, 600, 65536);
MessageBox.Show("Connected!");
}
catch (Exception exception)
{
MessageBox.Show($"Error {exception.Message}");
}
}
/// <summary>
/// Start a new thread used to receive SSH data when the window is loaded
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
ThreadStart threadStart = new ThreadStart(RecvSshData);
Thread thread = new Thread(threadStart);
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// Receive SSH data and write it to the textbox
/// </summary>
private void RecvSshData()
{
while (true)
{
try
{
if (_shellStream != null && _shellStream.DataAvailable)
{
string data = _shellStream.Read();
textBox.Dispatcher.Invoke(new UpdateTextCallback(UpdateText), data);
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
Thread.Sleep(200);
}
}
/// <summary>
/// Write message to the textbox
/// </summary>
/// <param name="message"></param>
private void UpdateText(string message)
{
textBox.AppendText(message + "\r\n");
}
}
}
From what I have read in other posts it seems like this should work and should capture all of the data but it does not. There could be something I am doing wrong in my implementation or they may even be a better way to do it.
Any input with help or recommendations is appreciated.
I got it working to a certain extent. "StartRecording" begins to record the stream on a separate thread which works and just writes it to the console for now. This is able to receive all of the data that is printed to the terminal on my device.
The only issue that I am having now is that the data stops coming through after about a minute. I'm not sure what is happening yet but I think the ShellStream is disconnecting at some point and I'm not sure why.
private SshClient _sshClient;
private ShellStream _shellStream;
private StreamReader _reader;
private StreamWriter _writer;
public Recorder()
{
try
{
_sshClient = new SshClient(_hostname, _port, _username, _password);
_sshClient.Connect();
_shellStream = _sshClient.CreateShellStream("Terminal", 80, 60, 800, 600, 65536);
_reader = new StreamReader(_shellStream, Encoding.UTF8, true, 1024, true);
_writer = new StreamWriter(_shellStream) { AutoFlush = true };
}
catch (Exception e)
{
// TODO
Console.WriteLine(e);
}
}
/// <summary>
/// Begin recording the output of "routediagnostic on" command
/// </summary>
public void StartRecording()
{
try
{
IsRecording = true;
WriteStream("routediagnostic on");
// Start a background thread that will read in the data from the Pyng terminal
ThreadStart threadStart = ReceiveData;
Thread thread = new Thread(threadStart) {IsBackground = true};
thread.Start();
}
catch (Exception e)
{
// TODO
Console.WriteLine(e);
}
finally
{
IsRecording = false;
}
}
private void ReceiveData()
{
while (true)
{
try
{
if (_reader != null)
{
StringBuilder result = new StringBuilder();
string line;
while ((line = _reader.ReadLine()) != null)
{
result.AppendLine(line);
}
if (!string.IsNullOrEmpty(result.ToString()))
{
// TODO - Parse data at this point
Console.WriteLine(result.ToString());
}
}
}
catch (Exception e)
{
// TODO
Console.WriteLine(e);
}
Thread.Sleep(200);
}
}
private void WriteStream(string cmd)
{
_writer.WriteLine(cmd);
while (_shellStream.Length == 0)
{
Thread.Sleep(500);
}
}
Related
++++++ Link to example project ++++++
I have a file that can contain thousands of lines of logged messages. I am parsing this file and adding each line (as a log event) to a collection. This collection should then be shown in a ListView.
As below:
<ListView
Grid.Row="0"
Margin="5"
ItemsSource="{Binding SelectedSerilogFileLog.LogEvents}"
ScrollViewer.CanContentScroll="False"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectedItem="{Binding SelectedLogEvent}"
SelectionMode="Single">
The parsing of the file (this one contains 2500+ log events) and adding to the collection takes around 100ms. Then when the bound collection is updated with the ReplaceContent method (this suppresses the collectionchanged event firing on every item added) the GUI hangs, but I cannot see why or what can be causing this.
MainWindow.cs
...
/// <summary>
///
/// </summary>
public SerilogFileLog SelectedSerilogFileLog
{
get => selectedSerilogFileLog; set
{
if (selectedSerilogFileLog != null)
{
SelectedSerilogFileLog.OnSerilogParserFinished -= OnSerilogParserFinished;
SelectedSerilogFileLog.OnSerilogParserProgressChanged -= OnSerilogParserProgressChanged;
}
selectedSerilogFileLog = value;
if (selectedSerilogFileLog != null)
{
ParserProgress = 0;
SelectedSerilogFileLog.OnSerilogParserFinished += OnSerilogParserFinished;
SelectedSerilogFileLog.OnSerilogParserProgressChanged += OnSerilogParserProgressChanged;
sw.Start();
SelectedSerilogFileLog.Parse();
}
NotifyPropertyChanged(nameof(SelectedSerilogFileLog));
}
}
...
private void Button_Click(object sender, RoutedEventArgs e)
{
SelectedSerilogFileLog = null;
SelectedSerilogFileLog = new SerilogFileLog() { FilePath = "Application20210216.log" };
}
The parsing and loading of the items occurs in a separate Task.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace LargeListViewTest.Classes
{
public class SerilogFileLog : INotifyPropertyChanged
{
private LogEvent lastLogEvent;
private ObservableCollectionEx<LogEvent> logEvents;
private string name;
private string description;
private string filePath;
private Regex patternMatching;
private string matchExpression = #"^(?<DateTime>[^|]+)\| (?<Level>[^|]+) \| (?<MachineName>[^|]+) \| (?<Source>[^|]+) \| (?<Message>[^$]*)$";
public delegate void SerilogParserProgressHandler(int Percentage);
public delegate void SerilogParserFinishedHandler();
/// <summary>
///
/// </summary>
public event SerilogParserProgressHandler OnSerilogParserProgressChanged;
/// <summary>
///
/// </summary>
public event SerilogParserFinishedHandler OnSerilogParserFinished;
/// <summary>
/// Gets or sets the LogEvents.
/// </summary>
public ObservableCollectionEx<LogEvent> LogEvents { get => logEvents; private set { logEvents = value; NotifyPropertyChanged(nameof(LogEvents)); } }
/// <summary>
/// Gets or sets the Name.
/// </summary>
public string Name { get => name; private set { name = value; NotifyPropertyChanged(nameof(Name)); } }
/// <summary>
/// Gets or sets the Description.
/// </summary>
public string Description { get => description; private set { description = value; NotifyPropertyChanged(nameof(Description)); } }
/// <summary>
/// Gets or sets the FilePath.
/// </summary>
public string FilePath
{
get => filePath;
set
{
filePath = value;
Name = Path.GetFileNameWithoutExtension(value);
Description = FilePath;
}
}
/// <summary>
///
/// </summary>
public SerilogFileLog()
{
LogEvents = new ObservableCollectionEx<LogEvent>();
patternMatching = new Regex(matchExpression, RegexOptions.Singleline | RegexOptions.Compiled);
}
/// <summary>
///
/// </summary>
public void Parse()
{
Task task = Task.Factory.StartNew(() => { InternalParse(); });
}
/// <summary>
///
/// </summary>
private void InternalParse()
{
OnSerilogParserProgressChanged?.Invoke(0);
try
{
if (!string.IsNullOrWhiteSpace(FilePath))
{
Console.WriteLine("Starting parse for {0}", FilePath);
long currentLength = 0;
FileInfo fi = new FileInfo(FilePath);
if (fi.Exists)
{
Console.WriteLine("Parsing Serilog file: {0}.", FilePath);
fi.Refresh();
List<LogEvent> parsedLogEvents = new List<LogEvent>();
StringBuilder sb = new StringBuilder();
using (FileStream fileStream = fi.Open(FileMode.Open, FileAccess.Read, FileShare.Write))
using (var streamReader = new StreamReader(fileStream))
{
while (streamReader.Peek() != -1)
{
sb.Append(streamReader.ReadLine());
LogEvent newLogEvent = ParseLogEvent(sb.ToString());
if (newLogEvent != null)
{
parsedLogEvents.Add(newLogEvent);
lastLogEvent = newLogEvent;
}
OnSerilogParserProgressChanged?.Invoke((int)(currentLength * 100 / fi.Length));
currentLength = currentLength + sb.ToString().Length;
sb.Clear();
}
}
LogEvents.ReplaceContent(parsedLogEvents);
}
Console.WriteLine("Finished parsing Serilog {0}.", FilePath);
}
}
catch (Exception ex)
{
Console.WriteLine("Error parsing Serilog." + ex.Message);
}
OnSerilogParserProgressChanged?.Invoke(100);
SerilogParserFinishedHandler onSerilogParserFinished = OnSerilogParserFinished;
if (onSerilogParserFinished == null)
return;
OnSerilogParserFinished();
}
/// <summary>
///
/// </summary>
/// <param name="mes"></param>
/// <returns></returns>
private LogEvent ParseLogEvent(string mes)
{
LogEvent logEvent = new LogEvent();
Match matcher = patternMatching.Match(mes);
try
{
if (matcher.Success)
{
logEvent.Message = matcher.Groups["Message"].Value;
DateTime dt;
if (!DateTime.TryParse(matcher.Groups["DateTime"].Value, out dt))
{
Console.WriteLine("Failed to parse date {Value}", matcher.Groups["DateTime"].Value);
}
logEvent.DateTime = dt;
logEvent.Level = matcher.Groups["Level"].Value;
logEvent.MachineName = matcher.Groups["MachineName"].Value;
logEvent.Source = matcher.Groups["Source"].Value;
}
else
{
if ((string.IsNullOrEmpty(mes) || (!Char.IsDigit(mes[0])) || !Char.IsDigit(mes[1])) && lastLogEvent != null)
{
// seems to be a continuation of the previous line, add it to the last event.
lastLogEvent.Message += Environment.NewLine;
lastLogEvent.Message += mes;
logEvent = null;
}
else
{
Console.WriteLine("Message parsing failed.");
}
if (logEvent != null)
logEvent.Message = mes;
}
}
catch (Exception ex)
{
Console.WriteLine("ParseLogEvent exception." + ex.Message);
}
return logEvent;
}
#region INotify
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string p) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(p));
#endregion
}
}
I have an ObservableCollectionEx class that extends the default ObservableCollection, this class suppresses the collection changed event until all the items have been added/replaced.
/// <summary>
/// Adds the supplied items to the collection and raises a single <see cref="CollectionChanged"/> event
/// when the operation is complete.
/// </summary>
/// <param name="items">The items to add.</param>
public void AddRange(IEnumerable<T> items, bool notifyAfter = true)
{
if (null == items)
{
throw new ArgumentNullException("items");
}
if (items.Any())
{
try
{
SuppressChangeNotification();
CheckReentrancy();
foreach (var item in items)
{
Add(item);
}
}
finally
{
if (notifyAfter)
FireChangeNotification();
suppressOnCollectionChanged = false;
}
}
}
/// <summary>
/// Replaces the content of the collection with the supplied items and raises a single <see cref="CollectionChanged"/> event
/// when the operation is complete.
/// </summary>
/// <param name="items">The items to replace the current content.</param>
public void ReplaceContent(IEnumerable<T> items)
{
SuppressChangeNotification();
ClearItems();
AddRange(items);
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (!suppressOnCollectionChanged)
{
#if NoCrossThreadSupport
base.OnCollectionChanged(e);
#else
using (BlockReentrancy())
{
NotifyCollectionChangedEventHandler eventHandler = CollectionChanged;
if (eventHandler == null)
return;
Delegate[] delegates = eventHandler.GetInvocationList();
// Walk the invocation list
foreach (NotifyCollectionChangedEventHandler handler in delegates)
{
DispatcherObject dispatcherObject = handler.Target as DispatcherObject;
// If the subscriber is a DispatcherObject and different thread
if (dispatcherObject != null && !dispatcherObject.CheckAccess())
{
// Invoke handler in the target dispatcher's thread
dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
}
else // Execute handler as is
handler(this, e);
}
}
#endif
}
}
I have tried using a List but I got the same behaviour.
Any ideas?
I have a button that skips a video on by x seconds. if a user spam clicks that button my video updates over and over again which is an expensive operation. What is the best way to stop a user spamming the button? I am using a routed ui command and want to add up the seconds and do 1 operation. Is a delay timer best practice here? delay the operation for 10ms and reset the delay on every click? or is there something built into wpf that can help?
UPDATE:
I would like to track the number of clicks a user is making during the spam click of the button
I really hope the async way works, while we were trying that out i created a solution, feel free to tell me all i did wrong and any bad practices.
I decided to use dispatcher timer for this even though i didn't really want to. couldn't find any better practices online.
private TimeSpan overallSkipSpeed = TimeSpan.Zero;
private readonly TimeSpan Interval = TimeSpan.FromMilliseconds(400);
private DispatcherTimer _dispatcherTimer;
private TimeSpan _time;
// Execute command function
public void ExecuteClickCommand()
{
// If the timer isn't going create and start it
if (_dispatcherTimer == null)
{
overallSkipSpeed = TimeSpanToModifyBy(skipSpeed, skipForward);
_time = Interval;
_dispatcherTimer = new DispatcherTimer(Interval, DispatcherPriority.Normal, Tick, Application.Current.Dispatcher);
_dispatcherTimer.Start();
}
else // If the timer is going reset to interval
{
// THIS IS WHERE I ADDED MY SKIP VALUES TOGETHER
// So value from last click + value from this click
_dispatcherTimer.Stop();
_time = Interval;
_dispatcherTimer.Start();
}
}
// Method to run when timer ticks over
private void Tick(object sender, EventArgs eventArgs)
{
// if the timer has reached below zero
if (_time <= TimeSpan.Zero)
{
_dispatcherTimer.Stop();
_dispatcherTimer = null;
_time = TimeSpan.FromSeconds(0);
// HERE IS WHERE WE CAN NOW SKIP VIDEO BY
// THE SKIP SPEED WE HAVE ACCUMULATED
}
else
{
_time = _time.Add(-Interval);
}
}
I have gone one step further with this and created my own command.
This command works like a relay command but will delay if you set a delay time span in initialisation. you can retrieve the number of times it was clicked in your execute method.
Initialise the command:
ICommand DelayedClickCommand = new DelayedCommand(ExecuteDelayedClickCommand, TimeSpan.FromMilliseconds(200));
Create an execute method and retrive the amount of times clicked:
private void ExecuteClickCommand()
{
TimesClicked = ((DelayedCommand)ClickCommand).TimesClicked;
}
and here is the command class:
public class DelayedCommand : ICommand
{
private readonly Action _methodToExecute;
private readonly Func<bool> _canExecuteEvaluator;
private readonly DispatcherTimer _dispatcherTimer;
public int TimesClicked;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// A command to stop the spamming of the <see cref="Execute"/> method
/// </summary>
/// <param name="methodToExecute">Method to run when command executes</param>
/// <param name="canExecuteEvaluator">Method used to determine if the command can execute</param>
/// <param name="delayTime">The cool down period required between click execution</param>
public DelayedCommand(Action methodToExecute, Func<bool> canExecuteEvaluator, TimeSpan delayTime)
{
_methodToExecute = methodToExecute;
_canExecuteEvaluator = canExecuteEvaluator;
_dispatcherTimer = new DispatcherTimer(delayTime, DispatcherPriority.Normal, Callback, Application.Current.Dispatcher);
}
/// <summary>
/// A command to stop the spamming of the <see cref="Execute"/> method
/// when no <see cref="CanExecute"/> method is required
/// </summary>
/// <param name="methodToExecute">Method to run when command executes</param>
/// <param name="delayTime">The cool down period required between click execution</param>
public DelayedCommand(Action methodToExecute, TimeSpan delayTime)
: this(methodToExecute, null, delayTime)
{
}
/// <summary>
/// A command when only a <see cref="Execute"/> method is needed
/// </summary>
/// <param name="methodToExecute">Method to run when command executes</param>
public DelayedCommand(Action methodToExecute)
: this(methodToExecute, null, TimeSpan.Zero)
{
}
/// <summary>
/// A command taking a <see cref="Execute"/> Method and a <see cref="CanExecute"/> method
/// </summary>
/// <param name="methodToExecute">Method to run when command executes</param>
/// <param name="canExecuteEvaluator">Method used to determine if the command can execute</param>
public DelayedCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
: this(methodToExecute, canExecuteEvaluator, TimeSpan.Zero)
{
}
public bool CanExecute(object parameter)
{
if (_canExecuteEvaluator == null)
{
return true;
}
return _canExecuteEvaluator.Invoke();
}
public void Execute(object parameter)
{
if (!_dispatcherTimer.IsEnabled)
TimesClicked = 0;
TimesClicked++;
_dispatcherTimer?.Stop();
_dispatcherTimer?.Start();
}
private void Callback(object sender, EventArgs eventArgs)
{
_dispatcherTimer.Stop();
_methodToExecute.Invoke();
}
}
Note: that when you spam click this command execute will not run untill 200ms after the last click was performed, giving a lagging effect. I have added a sample project to git hub and will add better commands for this question on there
https://github.com/sgreaves1/DelayedCommands
Guess i'm the laziest person here...
public class PostponeCommand : ICommand
{
private TimeSpan _delay;
private Action<object> _command;
private CancellationTokenSource _cancellation;
public PostponeCommand(Action<object> command, int delayMs)
{
this._command = command;
this._delay = TimeSpan.FromMilliseconds(delayMs);
}
public bool CanExecute(object parameter)
{
return true;
}
public async void Execute(object parameter)
{
_cancellation?.Cancel();
_cancellation = new CancellationTokenSource();
try
{
await Task.Delay(_delay, _cancellation.Token);
_command?.Invoke(parameter);
}
catch (TaskCanceledException ex)
{
// canceled
}
}
public event EventHandler CanExecuteChanged;
}
Not sure about build-in Command ability to do this, but you can do it with delay (updated based on comments):
private int spamCount = 0;
private int delayValue = 0;
private object isHoldedLock = new object();
private bool isHolded = false;
public bool CanProceed(int delay, Action updateVideo)
{
lock (this.isHoldedLock)
{
if (this.isHolded)
{
this.spamCount++;
this.delayValue = delay;
return false;
}
this.isHolded = true;
this.delayValue = delay;
Task.Run(async () =>
{
while (this.delayValue > 0)
{
await Task.Delay(100);
this.delayValue -= 100;
}
updateVideo();
lock (this.isHoldedLock)
{
this.isHolded = false;
}
});
return true;
}
}
Process/reset spamCount value inside SkipVideo any way you need.
And using in your command handler:
private void InvokedFromCommand()
{
if (CanProceed(1000, SkipVideo()))
{
// SkipVideo();
}
}
I have a Winforms application that calls CorelDraw via it's API and then does some work on an image before saving it. I have tried setting this using Threads, BackgroundWorker, and a class that inherited BackgroundWorker but also allow for Thread.Abort. Every way I have done the work, I continuously run into one of 2 issues. Either the thread is not run in the background, in which case I am unable to press any buttons on the form because it is frozen or the thread runs in the background but immediately stops processing anything when I hit the first call to CorelDraw.
Are there any alternative solutions? The old way we handled this was by having a watcher WinForm spawn another Process() that would do everything in CorelDraw. The process called with Process() saves important information into a .txt file for the parent process to check and if the child works on the same image for too long, the parent calls Kill() on it. So far, this has been a clunky and error prone way to do things, which is why I am looking for a better solution.
Designer Code:
namespace Tasks
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(41, 43);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(131, 43);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 1;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(240, 116);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
}
}
This is the form code:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Tasks
{
public partial class Form1 : Form
{
CancellationTokenSource cts;
public Form1()
{
InitializeComponent();
}
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show(this,"This button still works :)");
}
private async void button1_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
await CreateTask();
}
private async Task CreateTask()
{
//Create a progress object that can be used within the task
Progress<string> mProgress; //you can set this to Int for ProgressBar
//Set the Action to a function that will catch the progress sent within the task
Action<string> progressTarget = ReportProgress;
//Your new Progress with the included action function
mProgress = new Progress<string>(progressTarget);
//start your task
string result = await MyProcess(mProgress);
}
private Task<string> MyProcess(IProgress<string> myProgress)
{
return Task.Run(() =>
{
//To report Progress back to your UI thread
myProgress.Report("Starting program now...");
//Start your Corel Draw program here.
Process.Start("C:\\WINDOWS\\system32\\notepad.exe").WaitForExit();
//You can return the Image after your done editing it
return "Program has been closed";
}, cts.Token);
}
private void ReportProgress(string message)
{
//typically to update a progress bar or whatever
MessageBox.Show(this, message);
}
}
}
If you create async task you can run your Corel application in the background and then when it is closed it will be finish the task. I added in extra stuff in there like Progress and cts is used to cancel the task in case the parent window is closed.
I have clients thats is connected to a duplex wcf service 24/6 it is restarted every sunday.
In the client iam using a listview to display some information.
The listview itemssource is binded to a custom ObservableCollection.
The client is calling a keepalive method every minute to the wcf service.
My problem here is the client works fine when there is activity in the client. But when there is no activity and the application just run the keepalive method for about 10-16 hours. And iam trying to add and remove data to the listview it seems that nothing works. But the wcf service logging the method add and remove is working fine. Its like the userinterface isnt updating. When i restart the application everything works fine.
how do i solve this problem ?
My custom ObservableCollection object code :
public class ObservableOrderResponseQueue : INotifyCollectionChanged, IEnumerable<OrderResponse>
{
public event NotifyCollectionChangedEventHandler CollectionChanged = (o, e) => { };
private List<OrderResponse> _list = new List<OrderResponse>();
/// <summary>
/// Adds to the list.
/// </summary>
/// <param name="orderResponse">OrderResponse.</param>
public void Add(OrderResponse orderResponse)
{
//Only 6 items in list is possible if more then 6 remove the first item.
if (_list.Count >= 6)
{
RemoveAt(0);
}
this._list.Add(orderResponse);
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, orderResponse, (_list.Count - 1)));
}
/// <summary>
/// Remove from list.
/// </summary>
/// <param name="index">Item index to remove.</param>
public void RemoveAt(int index)
{
OrderResponse order = this._list[index];
this._list.RemoveAt(index);
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, order, index));
}
/// <summary>
/// Remove from list.
/// </summary>
/// <param name="orderResponse">Item to be removed.</param>
public void Remove(OrderResponse orderResponse)
{
if (_list.Count == 0) return;
var item = _list.Where(o => o.OrderDetail.TrayCode == orderResponse.OrderDetail.TrayCode).FirstOrDefault();
int index = _list.IndexOf(item);
if (index == -1) return;
CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
this._list.RemoveAt(index);
}
#region IEnumerable<OrderResponse> Members
public IEnumerator<OrderResponse> GetEnumerator()
{
return _list.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return _list.GetEnumerator();
}
#endregion
}
Here is how i bind the usercontrol to the class.
//Set up client callbacks events
App.clientBack.ClientNotified += new ClientNotifiedEventHandler(clientBack_ClientNotified);
App.clientBack.AddToDisplayEvent += new AddToDisplayEventHandler(clientBack_AddToDisplayEvent);
App.clientBack.RemoveFromDisplayEvent += new RemoveFromDisplayEventHandler(clientBack_RemoveFromDisplayEvent);
App.clientBack.UpdateQueueDisplayEvent += new UpdateQueueDisplayEventHandler(clientBack_UpdateQueueDisplayEvent);
//Show one chair or many.
if (_settings.IsOneChair)
{
userControlOneChair.ItemSource = _queueProductionItems;
}
else
{
userControlChairs.ItemsSource = _queueProductionItems;
}
Remove and add methods
void clientBack_RemoveFromDisplayEvent(object sender, RemoveFromDisplayEventArgs e)
{
try
{
_logger.Info("Remove from display.");
userControlChairs.Dispatcher.Invoke((Action)(() =>
{
_queueProductionItems.Remove(e.OrderResponse);
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void clientBack_AddToDisplayEvent(object sender, AddToDisplayEventArgs e)
{
try
{
_logger.Info("Add to display.");
userControlChairs.Dispatcher.Invoke((Action)(() =>
{
_queueProductionItems.Add(e.OrderResponse);
}));
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Thanks for help!
What i did was implementing a Heartbeat mechanism. And it all worked out.
I am still grokking attached behaviors in general, and am at a loss to see how to write a unit test for one.
I pasted some code below from Sacha Barber's Cinch framework that allows a window to be closed via attached behavior. Can somewone show me an example unit test for it?
Thanks!
Berryl
#region Close
/// <summary>Dependency property which holds the ICommand for the Close event</summary>
public static readonly DependencyProperty CloseProperty =
DependencyProperty.RegisterAttached("Close",
typeof(ICommand), typeof(Lifetime),
new UIPropertyMetadata(null, OnCloseEventInfoChanged));
/// <summary>Attached Property getter to retrieve the CloseProperty ICommand</summary>
public static ICommand GetClose(DependencyObject source)
{
return (ICommand)source.GetValue(CloseProperty);
}
/// <summary>Attached Property setter to change the CloseProperty ICommand</summary>
public static void SetClose(DependencyObject source, ICommand command)
{
source.SetValue(CloseProperty, command);
}
/// <summary>This is the property changed handler for the Close property.</summary>
private static void OnCloseEventInfoChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var win = sender as Window;
if (win == null) return;
win.Closing -= OnWindowClosing;
win.Closed -= OnWindowClosed;
if (e.NewValue == null) return;
win.Closing += OnWindowClosing;
win.Closed += OnWindowClosed;
}
/// <summary>
/// This method is invoked when the Window.Closing event is raised.
/// It checks with the ICommand.CanExecute handler
/// and cancels the event if the handler returns false.
/// </summary>
private static void OnWindowClosing(object sender, CancelEventArgs e)
{
var dpo = (DependencyObject)sender;
var ic = GetClose(dpo);
if (ic == null) return;
e.Cancel = !ic.CanExecute(GetCommandParameter(dpo));
}
/// <summary>
/// This method is invoked when the Window.Closed event is raised.
/// It executes the ICommand.Execute handler.
/// </summary>
static void OnWindowClosed(object sender, EventArgs e)
{
var dpo = (DependencyObject)sender;
var ic = GetClose(dpo);
if (ic == null) return;
ic.Execute(GetCommandParameter(dpo));
}
#endregion
You would likely use a lambda in your ICommand using a DelegateCommand or a RelayCommand. Multiple implementations of these exists all over the place and Cinch may have something similar. Really simple version (as an example, not meant for production use):
public class DelegateCommand : ICommand {
private Action _execute = null;
public void Execute( object parameter ) {
_execute();
}
public DelegateCommand( Action execute ) {
_execute = execute;
}
#region stuff that doesn't affect functionality
public bool CanExecute( object parameter ) {
return true;
}
public event EventHandler CanExecuteChanged {
add { }
remove { }
}
#endregion
}
Then your test body might look something like this:
bool wascalled = false;
var execute = new DelegateCommand(
() => {
wascalled = true;
} );
var window = new Window();
SomeClass.SetClose( window, execute );
// does the window need to be shown for Close() to work? Nope.
window.Close();
AssertIsTrue( wascalled );
This is an over-simplified example. There are of course other tests you'll want to perform, in which case you should create or find a fuller implementation of DelegateCommand that also properly implements CanExecute, among other things.
DependencyProperty changing and value coercion on their own looks like 'Impossible Dependencies' for me. Having reference to Window there makes things even trickier. I think I'd go with Humble Object pattern here...