I'm new to the C# world and I'm trying to create my first project.
I want to create application that read Ant+ heart rate sensors and analyze the data received.
I've created 2 classes
public class Athlete
{
public string Name { get; set; }
public string Surname { get; set; }
public int HBrate { get; set; }
public int HBcount { get; set; }
}
}
public class AntSensor
{
public string Name { get; set; }
public string SensorID { get; set; }
public byte ChannelNr { get; set; }
}
}
After selecting the Athlete and the sensor from a UI I initialize the Heart Rate sensor and open the receiver channel
public void AthleteStart()
{
if ((AthleteSelected != null) || (ANTSensorSelected != null))
{
AthleteLabelTxt = $"{AthleteSelected.Name}";
ANT_Device device0 = new ANT_Device();
ANT_Channel channel0 = device0.getChannel(0x00);
HRProject.AntApps(AthleteSelected, device0, channel0, new string[0]);
}
}
then after some code that control the communication protocol I have:
static void DeviceResponse(ANT_Response response)
{
// some code to control the response
// and the a call to Update my sensor data
SensorUpdate(response.getDataPayload());
}
how I can put here some code that update HBrate or HBcount on my AthleteSelected or ANTSensorSelected?
As Tim Schmelter already commented: The given information are not that much useful to find out at which point you are failing.
But to try to answer your question in a more general manner:
Updating a class (but you mean a class-instance) property from a static method, requires to give the class instance as a parameter to that method.
public class MyInstanceClass
{
public string MyValue { get; set; }
}
public static class MyStaticClass
{
public static void DoSomething(MyInstanceClass instance)
{
instance.MyValue = "New Value";
}
}
I don't use ANTSensorSelected because I was thinking to update directly AthleteSelected but if need I can eventually add.
for the communication I'm using the ANT_Managed_Library from Garmin here the ANT_Response class:
public class ANT_Response
{
/// <summary>
/// The object that created this response (ie: The corresponding ANTChannel or ANTDevice instance).
/// </summary>
public object sender;
/// <summary>
/// The channel parameter received in the message. Note: For some messages this is not applicable.
/// </summary>
public byte antChannel;
/// <summary>
/// The time the message was received.
/// </summary>
public DateTime timeReceived;
/// <summary>
/// The MessageID of the response
/// </summary>
public byte responseID;
/// <summary>
/// The raw contents of the response message
/// </summary>
public byte[] messageContents;
internal ANT_Response(object sender, byte antChannel, DateTime timeReceived, byte IDcode, byte[] messageContents)
{
this.sender = sender;
this.antChannel = antChannel;
this.timeReceived = timeReceived;
this.responseID = IDcode;
this.messageContents = messageContents;
}
The code it's quite long let me try to add the most significant parts:
channel0.channelResponse += new dChannelResponseHandler(ChannelResponse); // Add channel response function to receive channel event messages
/// Delegate for Channel Response Event
/// </summary>
/// <param name="response">Message details received from device</param>
public delegate void dChannelResponseHandler(byte antChannel, ANT_Response response);
here the ChannelResponse
static void ChannelResponse(byte antChannel, ANT_Response response)
{
try
{
switch ((ANT_ReferenceLibrary.ANTMessageID)response.responseID)
{
case ANT_ReferenceLibrary.ANTMessageID.RESPONSE_EVENT_0x40:
//some extra code with more cases
SensorUpdate(response.getDataPayload());
here the getDataPayLoad
/// Returns the 8-byte data payload of an ANT message. Throws an exception if this is not a received message.
/// </summary>
/// <returns></returns>
public byte[] getDataPayload()
{
if (messageContents.Length == 9
&& (responseID == (byte)ANT_ReferenceLibrary.ANTMessageID.BROADCAST_DATA_0x4E
|| responseID == (byte)ANT_ReferenceLibrary.ANTMessageID.ACKNOWLEDGED_DATA_0x4F
|| responseID == (byte)ANT_ReferenceLibrary.ANTMessageID.BURST_DATA_0x50
))
return messageContents.Skip(1).ToArray(); //Just skip the channel byte and return the payload
else
return splitExtMessage(extMsgParts.DataPayload); // Extended message
}
Related
I have a class
public class ProductionQueue
{
/// <summary>
/// Gets or sets the title.
/// </summary>
/// <value>
/// The title.
/// </value>
public string? Name { get; set; }
/// <summary>
/// Maximum capacity of the queue. Keep it null for inifinte capacity.
/// </summary>
public TimeSpan? Capacity { get; set; }
/// <summary>
/// Gets or sets the tasks.
/// </summary>
/// <value>
/// The tasks.
/// </value>
public IList<Task> Tasks { get; set; } = new List<Task>();
}
Let me just explain that the Task object also contains a variable (probably another Timespan) that contains the time to perform the task.
Is it possible to add an event, an observer or any other behaviour in my ProductionQueue to throw an exception if my Capacity is reached when I add a new task in my Task list?
I have written this simple solution based on your initial code but with using an ObservableCollection, so the capacity can be check every time the collection is modified.
It could be done in more effecient way using the data provided by NotifyCollectionChangedEventArgs (type of modification, NewItems, OldItems etc.) to recompute only what's changed, instead of iterating through the whole collection every time.
// simple object for the sake of this example
public class Task
{
public TimeSpan TimeToPerform;
}
public class ProductionQueue
{
public string? Name { get; set; }
/// <summary>
/// Maximum capacity of the queue. Keep it null for inifinte capacity.
/// </summary>
public TimeSpan? Capacity { get; set;}
public ObservableCollection<Task> Tasks = new ObservableCollection <Task>();
// ProductionQueue ctor
public ProductionQueue(TimeSpan? initialCapacity)
{
this.Capacity = initialCapacity;
// subscribe to know when the collection gets changed
this.Tasks.CollectionChanged += (s, e) => {
Console.WriteLine("collection changed");
this.checkCapacity();
};
}
private void checkCapacity()
{
var totalTime = TimeSpan.Zero;
foreach (var task in this.Tasks)
{
totalTime+= task.TimeToPerform;
}
if (totalTime > this.Capacity)
throw new Exception("queue time capacity exceeded");
}
}
}
And here is an example of program using it:
public static void Main(string[] args)
{
try
{
var PQ = new ProductionQueue(TimeSpan.FromHours(1));
PQ.Tasks.Add(new Task(){ TimeToPerform=TimeSpan.FromMinutes(40)});
PQ.Tasks.Add(new Task(){ TimeToPerform=TimeSpan.FromMinutes(30)});
Console.WriteLine("Tasks added without problem");
}
catch(Exception e)
{
Console.WriteLine("Exception occured: "+e.Message);
}
}
Console output:
collection changed
collection changed
Exception occured: queue time capacity exceeded
I've got some methods in one class that return values of to,from,message and im trying to use these in the other class that has the default display message.
I can't seem to use the string values that I get from the methods in class 1 in class 2.
I have tried declaring the string values public but got overloaded with errors none of which really said why the error was happening.
public class ChristmasCard
{
public static void Main()
{
string toWhom = To();
string fromWhom = From();
double decorate = ChooseDecoration();
string message = AddMessage();
DoPromt(message);
DisplayMessage(decorate);
Console.ReadLine();
}
public class ChristmasCardTesting : ChristmasCard
{
public static void SantaA()
{
Console.WriteLine(ChristmasCard.toWhom);
Console.WriteLine(ChristmasCard.Message1);
Console.WriteLine(ChristmasCard.Message2);
Console.WriteLine(ChristmasCard.Message3);
Console.WriteLine(ChristmasCard.fromWhom);
Console.ReadLine();
I guess inheritance is not what you want here. You want to use an instance of your object instead.
First you want to create an instance of your ChristmasCard.
That instance should hold Properties/Fields of the values you like to hold in the RAM.
Then you want to create an instance of your ChristmasCardTesting and call the "testmethod" while giving the christmas card as parameter.
And that code could be executed in your Program.
I guess what you want to achieve should more like the following:
public class Program {
public static void Main(string[] args) {
ChristmasCard card = new ChristmasCard();
ChristmasCardController controller = new ChristmasCardController();
controller.SomeMethod(card);
WriteToConsole(card);
}
public static void WriteToConsole(ChristmasCard card) {
Console.WriteLine(card.ToWhom);
Console.WriteLine(card.Message);
Console.WriteLine(card.FromWhom);
Console.ReadLine();
}
}
/// <summary>
/// Pure data class does not needs methods!
/// </summary>
public class ChristmasCard {
public string ToWhom { get; set; }
public string FromWhom { get; set; }
public double Decorate { get; set; }
public string Message { get; set; }
}
/// <summary>
/// Controller for the dataClass
/// </summary>
public class ChristmasCardController {
public void SomeMethod(ChristmasCard card) {
card.ToWhom = To();
card.FromWhom = From();
card.Decorate = ChooseDecoration();
card.Message = AddMessage();
DoPromt(card);
DisplayMessage(card.Decorate);
}
private void DisplayMessage(double cardDecorate) {
//Write your message to the console
}
private void DoPromt(ChristmasCard card) {
//Do some ConsoleRead in here
}
private string AddMessage() {
throw new NotImplementedException();
}
private double ChooseDecoration() {
throw new NotImplementedException();
}
private string From() {
throw new NotImplementedException();
}
private string To() {
throw new NotImplementedException();
}
}
Edit:
If that is totally not what you want, please explain what you are trying to achieve and i'm 100% sure i'll find the answer.
Edit2
All methods from that Controller class could also be in the Program class.
Note these would have to be static if you move these methods. I hope this snipped makes it clear how to solve the problem.
So I have two laptops connected to the same wifi network with one running a very simple server and the other a very simple client connecting to it. When I run both the server and the client on one laptop they connect without a problem, but when running one on each laptop the client cannot connect to the server.
The code for the server is this:
using System;
using System.Net;
using System.Net.Sockets;
namespace Utils
{
/// <summary>
/// A base server which handles listening for client connections and has simple API to communicate back and forth
/// </summary>
public class BaseServer
{
#region Properties and Fields
/// <summary>
/// The listener we can use to detect incoming connections from clients to the server
/// </summary>
private TcpListener Listener { get; set; }
/// <summary>
/// Our interface to the single client we are supporting for now
/// </summary>
public Comms ClientComms { get; private set; }
/// <summary>
/// Determines whether we have clients connected
/// </summary>
public bool Connections { get; private set; }
#endregion
public BaseServer()
{
Listener = new TcpListener(IPAddress.Any, 1490);
Listener.Start();
ListenForNewClient();
}
/// <summary>
/// Starts an asynchronous check for new connections
/// </summary>
private void ListenForNewClient()
{
Listener.BeginAcceptTcpClient(AcceptClient, null);
}
/// <summary>
/// Callback for when a new client connects to the server
/// </summary>
/// <param name="asyncResult"></param>
protected virtual void AcceptClient(IAsyncResult asyncResult)
{
ClientComms = new Comms(Listener.EndAcceptTcpClient(asyncResult));
ClientComms.OnDataReceived += ProcessMessage;
ListenForNewClient();
}
#region Message Callbacks
/// <summary>
/// A function which is called when the Client sends a message to the server.
/// Override to perform custom message handling
/// </summary>
/// <param name="data"></param>
protected virtual void ProcessMessage(byte[] data) { }
#endregion
}
}
And the code for the client is this:
using System;
using System.Net.Sockets;
namespace Utils
{
/// <summary>
/// A base client class which connects and communicates with a remote server
/// </summary>
public class BaseClient
{
#region Properties and Fields
/// <summary>
/// The interface to the server
/// </summary>
public Comms ServerComms { get; private set; }
#endregion
public BaseClient(string ipAddress, int portNumber = 1490)
{
// Attempt to connect
try
{
ServerComms = new Comms(new TcpClient(ipAddress, portNumber));
ServerComms.OnDataReceived += OnMessageReceived;
ServerComms.OnDisconnect += OnServerDisconnect;
}
catch (Exception e)
{
Console.WriteLine("Connection failed");
}
}
#region Callbacks
/// <summary>
/// A function which is called when this client receives a message.
/// Override to perform behaviour when custom messages arrive.
/// </summary>
/// <param name="data"></param>
protected virtual void OnMessageReceived(byte[] data) { }
/// <summary>
/// A function called when this client can no longer communicate to the server it is connected to
/// </summary>
protected virtual void OnServerDisconnect() { }
#endregion
}
}
The server is started from the main loop like this:
using System;
namespace BuildServer
{
class Program
{
static void Main(string[] args)
{
BaseServer server = new BaseServer();
while (true)
{
}
}
}
}
and the Client is started like this:
using System;
using Utils;
namespace BuildServerClient
{
class Program
{
static void Main(string[] args)
{
BaseClient client = new BaseClient();
while (true)
{
Console.WriteLine("Ready");
string message = Console.ReadLine();
client.ServerComms.Send(message);
}
}
}
}
One final class is a Comms class which is really a wrapper around the TCPClient and not really used currently, but I am adding it for the same of completeness.
using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using static Utils.Delegates;
namespace Utils
{
/// <summary>
/// An interface to a client.
/// Hides the nuts and bolts and provides a public interface of just data input and output from a data sender/receiver.
/// </summary>
public class Comms
{
#region Properties and Fields
private TcpClient Client { get; set; }
private MemoryStream ReadStream { get; set; }
private MemoryStream WriteStream { get; set; }
private BinaryReader Reader { get; set; }
private BinaryWriter Writer { get; set; }
/// <summary>
/// Useful buffer for reading packeted messages from the server
/// </summary>
private byte[] ReadBuffer { get; set; }
/// <summary>
/// An event that is fired when this Comms receives a message
/// </summary>
public event OnDataReceived OnDataReceived;
/// <summary>
/// An event that is fired when this Comms can no longer communicate with the client sending it messages
/// </summary>
public event OnDisconnect OnDisconnect;
#endregion
public Comms(TcpClient client)
{
Client = client;
ReadStream = new MemoryStream();
WriteStream = new MemoryStream();
Reader = new BinaryReader(ReadStream);
Writer = new BinaryWriter(WriteStream);
ReadBuffer = new byte[2048];
Client.NoDelay = true;
StartListening();
}
#region Data Sending Functions
/// <summary>
/// Convert a string to a byte array and then send to our client
/// </summary>
/// <param name="client"></param>
/// <param name="str"></param>
public void Send(string str)
{
SendByteArray(Encoding.UTF8.GetBytes(str));
}
/// <summary>
/// Send a byte array to our client
/// </summary>
/// <param name="client"></param>
/// <param name="bytes"></param>
protected void SendByteArray(byte[] bytes)
{
Writer.Write(bytes);
int bytesWritten = (int)WriteStream.Position;
byte[] result = new byte[bytesWritten];
WriteStream.Position = 0;
WriteStream.Read(result, 0, bytesWritten);
WriteStream.Position = 0;
Client.GetStream().BeginWrite(result, 0, result.Length, null, null);
Writer.Flush();
}
#endregion
#region Data Receiving Functions
/// <summary>
/// Start listening for messages from the server
/// </summary>
private void StartListening()
{
try
{
Client.GetStream().BeginRead(ReadBuffer, 0, 2048, StreamReceived, null);
}
catch
{
OnDisconnect?.Invoke();
}
}
/// <summary>
/// Callback which processes a message sent from the server
/// </summary>
/// <param name="ar"></param>
private void StreamReceived(IAsyncResult ar)
{
int bytesRead = 0;
try
{
lock (Client.GetStream())
{
bytesRead = Client.GetStream().EndRead(ar);
}
}
catch { }
//Create the byte array with the number of bytes read
byte[] data = new byte[bytesRead];
//Populate the array
for (int i = 0; i < bytesRead; i++)
{
data[i] = ReadBuffer[i];
}
OnDataReceived?.Invoke(data);
//Listen for new data
StartListening();
}
#endregion
}
}
The IP Addresses and Port numbers are correct and since it works when running on the same machine, I was thinking it might be a firewall issue or something? Does anyone have any ideas as to what might be causing this problem?
Make sure the firewall (Windows Firewall, or whatever's there) is turned off on the server machine, or there's a firewall exception for your port number.
David's answer was correct. I previously tried it with just the firewall disabled for private networks. However, I disabled the firewall for guest and public networks and it worked a treat.
The method of testing proposed by codenoir was also very effective at ruling out my client. I suspected it was something to do with the firewall, but once you rule out the impossible...
Thanks to both
in my main WPF application code i need to run .exe app with command prompt. this action is executed inside backgroundworker i have the following code. the code is running readlines.exe app with command prompt and read the output lines into a string (str).
string str;
ProcessStartInfo proc = new ProcessStartInfo();
proc.WindowStyle = ProcessWindowStyle.Hidden;
proc.UseShellExecute = true;
proc.FileName = #"readlines.exe";
proc.Arguments = #"";
proc.UseShellExecute = false;
proc.RedirectStandardOutput = true;
proc.CreateNoWindow = true;
proc.RedirectStandardInput = true;
Process proc1 = Process.Start(proc);
proc1.StandardInput.WriteLine("");
str = proc1.StandardOutput.ReadToEnd();
i want to ad timeout to the below line so when the timeout will be finised the procces will be canceled (as CTR+C) and "str" will get the output text until this point.
str = proc1.StandardOutput.ReadToEnd();
is it possible?
Although the previous answer has already been accepted here is a maybe more useful, secure and performant solution. Further it does not make use of the ReadLine() method which would block until there has been one line written (which may never occur). It uses an instance of StringBuilder and reads from the stream in specifyable data blocks (default size is 128 characters). Furthermore it supports event based notification of read data.
The usage of the class stays the same.
ProcessOutputReader por = new ProcessOutputReader(proc1);
por.StartReading();
// Do whatever you want here
// (e.g. sleep or whatever)
por.StopReading();
// Now you have everything that has been read in por.Data
However I've added the OnDataRead event which is fired every time new data has been read. You can access the data by using e.g. following code:
...
// Subscribe to the event
por.OnDataRead += OnDataReadEventHandler;
...
The callback method / event handler would look something like this:
private void OnDataReadEventHandler(object sender, ProcessOutputReaderEventArgs e)
{
// e.IntermediateDataStore points to the StringBuilder instance which holds
// all the data that has been received until now.
string completeData = e.IntermediateDataStore.ToString();
// e.NewData points to a string which contains the data that has been received
// since the last triggered event (because the event is triggered on each read).
string newData = e.NewData;
}
The modified ProcessOutputReader class looks like this:
/// <summary>
/// Represents the ProcessOutputReader class.
/// </summary>
public class ProcessOutputReader
{
/// <summary>
/// Represents the instance of the thread arguments class.
/// </summary>
private ProcessOutputReaderWorkerThreadArguments threadArguments;
/// <summary>
/// Initializes a new instance of the <see cref="ProcessOutputReader"/> class.
/// </summary>
/// <param name="process">The process which's output shall be read.</param>
/// <exception cref="System.ArgumentOutOfRangeException">Is thrown if the specified process reference is null.</exception>
public ProcessOutputReader(Process process)
{
if (process == null)
{
throw new ArgumentOutOfRangeException("process", "The parameter \"process\" must not be null");
}
this.Process = process;
this.IntermediateDataStore = new StringBuilder();
this.threadArguments = new ProcessOutputReaderWorkerThreadArguments(this.Process, this.IntermediateDataStore);
}
/// <summary>
/// Is fired whenever data has been read from the process output.
/// </summary>
public event EventHandler<ProcessOutputReaderEventArgs> OnDataRead;
/// <summary>
/// Gets or sets the worker thread.
/// </summary>
private Thread ReaderThread
{
get;
set;
}
/// <summary>
/// Gets or sets the intermediate data store.
/// </summary>
private StringBuilder IntermediateDataStore
{
get;
set;
}
/// <summary>
/// Gets the data collected from the process output.
/// </summary>
public string Data
{
get
{
return this.IntermediateDataStore.ToString();
}
}
/// <summary>
/// Gets the process.
/// </summary>
public Process Process
{
get;
private set;
}
/// <summary>
/// Stars reading from the process output.
/// </summary>
public void StartReading()
{
if (this.ReaderThread != null)
{
if (this.ReaderThread.IsAlive)
{
return;
}
}
this.ReaderThread = new Thread(new ParameterizedThreadStart(ReaderWorker));
this.threadArguments.Exit = false;
this.ReaderThread.Start(this.threadArguments);
}
/// <summary>
/// Stops reading from the process output.
/// </summary>
public void StopReading()
{
if (this.ReaderThread != null)
{
if (this.ReaderThread.IsAlive)
{
this.threadArguments.Exit = true;
this.ReaderThread.Join();
}
}
}
/// <summary>
/// Fires the OnDataRead event.
/// </summary>
/// <param name="newData">The new data that has been read.</param>
protected void FireOnDataRead(string newData)
{
if (this.OnDataRead != null)
{
this.OnDataRead(this, new ProcessOutputReaderEventArgs(this.IntermediateDataStore, newData));
}
}
/// <summary>
/// Represents the worker method.
/// </summary>
/// <param name="data">The thread arguments, must be an instance of the <see cref="ProcessOutputReaderWorkerThreadArguments"/> class.</param>
private void ReaderWorker(object data)
{
ProcessOutputReaderWorkerThreadArguments args;
try
{
args = (ProcessOutputReaderWorkerThreadArguments)data;
}
catch
{
return;
}
try
{
char[] readBuffer = new char[args.ReadBufferSize];
while (!args.Exit)
{
if (args.Process == null)
{
return;
}
if (args.Process.HasExited)
{
return;
}
if (args.Process.StandardOutput.EndOfStream)
{
return;
}
int readBytes = this.Process.StandardOutput.Read(readBuffer, 0, readBuffer.Length);
args.IntermediateDataStore.Append(readBuffer, 0, readBytes);
this.FireOnDataRead(new String(readBuffer, 0, readBytes));
}
}
catch (ThreadAbortException)
{
if (!args.Process.HasExited)
{
args.Process.Kill();
}
}
}
}
In addition you need the ProcessOutputReaderWorkerThreadArguments class which looks like this:
/// <summary>
/// Represents the ProcessOutputReaderWorkerThreadArguments class.
/// </summary>
public class ProcessOutputReaderWorkerThreadArguments
{
/// <summary>
/// Represents the read buffer size,
/// </summary>
private int readBufferSize;
/// <summary>
/// Initializes a new instance of the <see cref="ProcessOutputReaderWorkerThreadArguments"/> class.
/// </summary>
/// <param name="process">The process.</param>
/// <param name="intermediateDataStore">The intermediate data store.</param>
public ProcessOutputReaderWorkerThreadArguments(Process process, StringBuilder intermediateDataStore)
{
this.ReadBufferSize = 128;
this.Exit = false;
this.Process = process;
this.IntermediateDataStore = intermediateDataStore;
}
/// <summary>
/// Gets or sets a value indicating whether the thread shall exit or not.
/// </summary>
public bool Exit
{
get;
set;
}
/// <summary>
/// Gets or sets the read buffer size in bytes.
/// </summary>
/// <exception cref="System.ArgumentOutOfRangeException">Is thrown if the specified value is not greather than 0.</exception>
public int ReadBufferSize
{
get
{
return this.readBufferSize;
}
set
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException("value", "The specified value for \"ReadBufferSize\" must be greater than 0.");
}
this.readBufferSize = value;
}
}
/// <summary>
/// Gets the process.
/// </summary>
public Process Process
{
get;
private set;
}
/// <summary>
/// Gets the intermediate data store.
/// </summary>
public StringBuilder IntermediateDataStore
{
get;
private set;
}
}
And the ProcessOutputReaderEventArgs class which looks like this:
/// <summary>
/// Represents the ProcessOutputReaderEventArgs class.
/// </summary>
public class ProcessOutputReaderEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="ProcessOutputReaderEventArgs"/> class.
/// </summary>
/// <param name="intermediateDataStore">The reference to the intermediate data store.</param>
/// <param name="newData">The new data that has been read.</param>
public ProcessOutputReaderEventArgs(StringBuilder intermediateDataStore, string newData)
{
this.IntermediateDataStore = intermediateDataStore;
this.NewData = newData;
}
/// <summary>
/// Gets the reference to the intermediate data store.
/// </summary>
public StringBuilder IntermediateDataStore
{
get;
private set;
}
/// <summary>
/// Gets the new data that has been read.
/// </summary>
public string NewData
{
get;
private set;
}
}
Some example how to achieve this (attention, code not tested and can be improved)
ProcessOutputReader por = new ProcessOutputReader(proc1);
por.StartReading();
// Do whatever you want here
// (e.g. sleep or whatever)
por.StopReading();
// Now you have everything that has been read in por.Lines
The class would look like:
public class ProcessOutputReader
{
public ProcessOutputReader(Process process)
{
this.Process = process;
this.Lines = new List<string>();
}
public List<string> Lines
{
get;
private set;
}
public Process Process
{
get;
private set;
}
private Thread ReaderThread
{
get;
set;
}
public void StartReading()
{
if (this.ReaderThread == null)
{
this.ReaderThread = new Thread(new ThreadStart(ReaderWorker));
}
if (!this.ReaderThread.IsAlive)
{
this.ReaderThread.Start();
}
}
public void StopReading()
{
if (this.ReaderThread != null)
{
if (this.ReaderThread.IsAlive)
{
this.ReaderThread.Abort();
this.ReaderThread.Join();
}
}
}
private void ReaderWorker()
{
try
{
while (!this.Process.HasExited)
{
string data = this.Process.StandardOutput.ReadLine();
this.Lines.Add(data);
}
}
catch (ThreadAbortException)
{
if (!this.Process.HasExited)
{
this.Process.Kill();
}
}
}
}
Today I've discovered that the FW 4.5 has their own undoredo manager (if I understood good) http://msdn.microsoft.com/en-us/library/System.ComponentModel.Design.UndoEngine%28v=vs.110%29.aspx
Well, I can't find any example about how to start using this class just to make a simple undo/redo of a text based control, I know other alternatives to do undoable things, but just I want to learn how to use this.
When I try to use the constructor it has any parameter to be passed, and also the Intellisense does not shows me any method for the System.ComponentModel.Design.UndoEngine class, really I don't know how I could use it.
Someone could illustrate ours with an example for C# or VBNET? (I preffer VBNET documentation if possibly, please)
UndoEngine is an abstract class, Visual Studio and Designers have implemented UndoEngine in their own way, and those must be private or not available for redistribution. You will not be able to use it, in fact abstract class is just an interface with little implementation, it is not at all an undo framework.
You still have to write your own undo management, however benefit of deriving your undo engine from UndoEngine class is, it can be easily hosted/integrated with VS and other MS based editors.
If you want to provide an editing experience inside Visual Studio document editor, then you have to derive your Undo framework class from UndoEngine, VS will automatically highlight disable undo/redo buttons and will call undo/redo methods on your class.
If you want to use UndoEngine inside your own application, UndoEngine will not help you for anything, you will have to write every functionality by yourself. UndoEngine just manages stack of Undo/Redo, real work is inside UndoUnit. It is based on Unit of Work concept, where your every action should actually represent a work that can be undone.
The simplest UndoEngine Implementation
Let us say you are changing a global variable,
// following code uses big UndoUnit
public void SetGlobalVariable(object v){
var oldValue = GlobalVariable;
GlobalVariable = v;
var action = new UndoUnit{
UndoAction = ()=>{
GlobalVariable = oldValue;
},
RedoAction = ()=>{
GlobalVariable = v;
}
};
UndoManager.Add(action);
}
/// <summary>
/// Used to indicates the designer's status
/// </summary>
public enum UndoUnitState
{
Undoing,
Redoing,
}
/// <summary>
/// A UndoUnitBase can be used as a IOleUndoUnit or just a undo step in
/// a transaction
/// </summary>
public class UndoUnitBase : IOleUndoUnit
{
public Action UndoAction {get;set;}
public Action RedoAction {get;set;}
private string name = null;
private Guid clsid = Guid.Empty;
private bool inDoAction = false;
private bool isStillAtTop = true;
private UndoUnitState unitState = UndoUnitState.Undoing;
protected UndoUnit UnitState
{
get { return unitState; }
set { unitState = value; }
}
/// <summary>
/// </summary>
/// <param name="undoManager"></param>
/// <param name="name"></param>
internal UndoUnitBase(string name)
{
this.name = name;
}
~UndoUnitBase()
{
}
/// <summary>
/// </summary>
protected bool InDoAction
{
get
{
return inDoAction;
}
}
public string UndoName
{
get
{
return name;
}
set
{
this.name = value;
}
}
public Guid Clsid
{
get { return clsid; }
set { clsid = value; }
}
/// <summary>
/// This property indicates whether the undo unit is at the top (most recently added to)
/// the undo stack. This is useful to know when deciding whether undo units for operations
/// like typing can be coallesced together.
/// </summary>
public bool IsStillAtTop
{
get { return isStillAtTop; }
}
/// <summary>
/// This function do the actual undo, and then revert the action to be a redo
/// </summary>
/// <returns>objects that should be selected after DoAction</returns>
protected abstract void DoActionInternal();
/// <devdoc>
/// IOleUndoManager's "Do" action.
/// </devdoc>
void IOleUndoUnit.Do(IOleUndoManager oleUndoManager)
{
Do(oleUndoManager);
}
protected virtual int Do(IOleUndoManager oleUndoManager)
{
try
{
if(unitState== UndoUnitState.Undoing){
UndoAction();
}else{
RedoAction();
}
unitState = (unitState == UndoUnitState.Undoing) ? UndoUnitState.Redoing : UndoUnitState.Undoing;
if (oleUndoManager != null)
oleUndoManager.Add(this);
return VSConstants.S_OK;
}
catch (COMException e)
{
return e.ErrorCode;
}
catch
{
return VSConstants.E_ABORT;
}
finally
{
}
}
/// <summary>
/// </summary>
/// <returns></returns>
void IOleUndoUnit.GetDescription(out string pBstr)
{
pBstr = name;
}
/// <summary>
/// </summary>
/// <param name="clsid"></param>
/// <param name="pID"></param>
/// <returns></returns>
void IOleUndoUnit.GetUnitType(out Guid pClsid, out int plID)
{
pClsid = Clsid;
plID = 0;
}
/// <summary>
/// </summary>
void IOleUndoUnit.OnNextAdd()
{
// We are no longer the top most undo unit; another one was added.
isStillAtTop = false;
}
}
public class MyUndoEngine : UndoEngine, IUndoHandler
{
Stack<UndoEngine.UndoUnit> undoStack = new Stack<UndoEngine.UndoUnit>();
Stack<UndoEngine.UndoUnit> redoStack = new Stack<UndoEngine.UndoUnit>();
public ReportDesignerUndoEngine(IServiceProvider provider) : base(provider)
{
}
#region IUndoHandler
public bool EnableUndo {
get {
return undoStack.Count > 0;
}
}
public bool EnableRedo {
get {
return redoStack.Count > 0;
}
}
public void Undo()
{
if (undoStack.Count > 0) {
UndoEngine.UndoUnit unit = undoStack.Pop();
unit.Undo();
redoStack.Push(unit);
}
}
public void Redo()
{
if (redoStack.Count > 0) {
UndoEngine.UndoUnit unit = redoStack.Pop();
unit.Undo();
undoStack.Push(unit);
}
}
#endregion
protected override void AddUndoUnit(UndoEngine.UndoUnit unit)
{
undoStack.Push(unit);
}
}
If your question is how to use it at runtime, then the answer is in MSDN:
Specifies generic undo/redo functionality at design time.
So I doubt that it is easily usable at runtime.
If you meant an example of custom user control utilizing this class, I can't find any, sorry.
Find an implementation of the UndoEngine and how to use it right here:
https://github.com/icsharpcode/SharpDevelop/search?q=ReportDesignerUndoEngine&ref=cmdform
HTH