I have a LogOperations class, which is a singleton, and it contains a collection of my Log object and a few methods for adding new Logs to the collection. Log only has 3 properties; Message, Exception, and Time.
On the only form in my application, I get a reference to LogOperations. Then I use its Collection<Log> logs property to build a data table out of each entry and throw that into a GridControl (gdcErrorLogs).
gdcErrorLog.DataSource = logOps.GetLogsAsDataTable(); //Popualtes dt with each Log in logs
I have a bunch of other classes that perform all sorts of functions for the form, and whenever these functions catch an error I write that error to my LogOperations's collection.
How can I make my form's grid automatically update with this new entry each time a Log gets added? I'm thinking of adding a reference to the form in my LogOperations class, and then at the end of my Add functions I'd just re-set the form's grid to be this new DataTable?
public class AddNewLogEventArgs : EventArgs
{
public Log log { get; private set; } // Error on compile see below
public AddNewLogEventArgs(Log newLog) // Error on compile see below
{
log = newLog;
}
}
class LogOps
{
public delegate void AddNewLogHandler(object sender, AddNewLogEventArgs e);
public event AddNewLogHandler OnAddNewLog;
private Collection<Log> _logs;
private static LogOps _singleton;
private LogOps()
{
_logs = new Collection<Log>();
}
public static LogOps Instance
{
get
{
if (_singleton == null)
_singleton = new LogOps();
return _singleton;
}
}
public void AddLog(string text)
{
Log newLog = new Log(text);
_singleton._logs.Add(newLog);
OnAddNewLog(new AddNewLogEventArgs(newLog), null);
}
public void AddLog(Log log)
{
Log newLog = log;
_singleton._logs.Add(newLog);
OnAddNewLog(new AddNewLogEventArgs(newLog), null);
}
public void AddLog(Exception ex)
{
Log newLog = new Log(ex.Message, ex.InnerException);
_singleton._logs.Add(newLog);
OnAddNewLog(new AddNewLogEventArgs(newLog), null);
}
public DataTable GetLogsAsDataTable()
{
DataTable dt = new DataTable();
dt.Columns.Add("Time");
dt.Columns.Add("Error");
dt.Columns.Add("Exception");
int i = 0;
foreach (Log log in _logs)
{
dt.Rows.Add();
dt.Rows[i].SetField("Time", log.Time);
dt.Rows[i].SetField("Error", log.Err);
dt.Rows[i].SetField("Exception", log.Ex.Message);
i++;
}
return dt;
}
}
Edit Code sample was heavily modified since trying out King King's solution
The two flagged lines in the AddNewLogEventArgs give me these two errors:
Error 1 Inconsistent accessibility: property type 'PrepareDeployment.Models.Log' is less accessible than property 'PrepareDeployment.Processes.AddNewLogEventArgs.log'
Error 2 Inconsistent accessibility: parameter type 'PrepareDeployment.Models.Log' is less accessible than method 'PrepareDeployment.Processes.AddNewLogEventArgs.AddNewLogEventArgs(PrepareDeployment.Models.Log)'
You should do something like this:
public delegate void AddNewLogEventHandler(object sender, AddNewLogEventArgs e);
public event AddNewLogEventHandler AddNewLog;
public class AddNewLogEventArgs : EventArgs {
public Log {get; private set;}
public AddNewLogEventArgs(Log log){
Log = log;
}
}
protected virtual void OnAddNewLog(AddNewLogEventArgs e){
AddNewLogEventHandler handler = AddNewLog;
if(handler != null) handler(this, e);
}
public void AddLog(Exception ex) {
Log newLog = new Log(ex.Message, ex.InnerException);
singleton._logs.Add(newLog);
OnAddNewLog(new AddNewLogEventArgs(newLog));
}
public void AddLog(string text) {
Log newLog = new Log(text);
singleton._logs.Add(newLog);
OnAddNewLog(new AddNewLogEventArgs(newLog));
}
In your main form (with your grid), you can do something like this:
LogOperations.Instance.AddNewLog += (s,e) => {
var dt = (DataTable) gdcErrorLog.DataSource;
dt.Rows.Add(e.Log.Time, e.Log.Err, e.Log.Ex.Message);
};
NOTE: You should remove the private frmMain frm; in your old class.
Related
Help!
I can not figure it out how to implement Serilog to output my logs in real time into a textbox from Winforms.
I have an application written in .Net C# that was written a long time ago and had the logging framework log4net. I had different appenders and one was created in my code :
public class ExAppender : AppenderSkeleton{
private IExAppender control = null;
public void AttachControl(IExAppender obj)
{ this.control = obj;}
protected override void Append(LoggingEvent loggingEvent)
{
try
{
string message = RenderLoggingEvent(loggingEvent);
if (this.control != null)
{
this.control.LogMessage(message, loggingEvent.Level.Name);
}
}catch{// ignore}
}
And after that I had another class defined ExLogger:
public static class ExLogger
{ private static readonly ILog LoggerObj = null;
public static bool AttachControl(IExAppender obj)
{
IAppender[] appenders = LoggerObj.Logger.Repository.GetAppenders();
(appender as ExAppender).AttachControl(obj);
return true;
}
return false;}
I defined my serilog loggers in app.config, I want to read them from there because i have multiple loggers, I think that I need to use public class ExAppender : ILogEventSink, I replaced the old code to be suitable for Serilog, it writes to files, to eventLog, console etc, BUT I could not found a way to attach a windows to the logger and to write there. After my modification I obtaind something like this:
public class ExAppender : ILogEventSink
{
public ExAppender control = null;
public ConcurrentQueue<string> Events { get; } = new ConcurrentQueue<string>();
public void AttachControl(IExAppender obj)
{
this.control = obj;
}
public void Emit(LogEvent logEvent)
{
if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
var renderSpace = new ExAppender();
Events.Enqueue(renderSpace.ToString());
try
{ string message = logEvent.RenderMessage();
if (this.control != null)
{
this.control.LogMessage(message, logEvent.Level.ToString());
}
}catch { }
}
And for the ExLogger class:
public static bool AttachControl( IExAppender obj)
{try
{
ILogger test = new LoggerConfiguration()
.ReadFrom.AppSettings(settingPrefix: "ExAppender")
.WriteTo.ExAppender(restrictedToMinimumLevel: LogEventLevel.Information)
.CreateLogger();
return true;
}catch
{
return false;
}}
Can someone guide me? Does someone has an example or maybe explain what am I missing?
Maybe I am a bit too late to help you, but this is how I implemented it:
Custom logger sink, which has EventHandler:
public class TbsLoggerSink : ILogEventSink
{
public event EventHandler NewLogHandler;
public TbsLoggerSink() { }
public void Emit(LogEvent logEvent)
{
#if DEBUG
Console.WriteLine($"{logEvent.Timestamp}] {logEvent.MessageTemplate}");
#endif
NewLogHandler?.Invoke(typeof(TbsCore.Helpers.TbsLoggerSink), new LogEventArgs() { Log = logEvent });
}
}
public class LogEventArgs : EventArgs
{
public LogEvent Log { get; set; }
}
When creating the Serilog logger, add your custom sink. I use static sink/logger so I can access it from anywhere.
public static TbsLoggerSink LoggerSink = new TbsLoggerSink();
public static readonly Serilog.Core.Logger Log = new LoggerConfiguration()
.WriteTo.Sink(LoggerSink)
.CreateLogger();
Than in your view/form, where you have TextBox/RichTextBox (in my case this.logTextBox), add event handler:
public partial class MyUserControl : UserControl
{
public MyUserControl()
{
InitializeComponent();
Utils.LoggerSink.NewLogHandler += LogHandler;
}
private void LogHandler(object sender, EventArgs e)
{
var log = ((LogEventArgs)e).Log;
this.logTextBox.Text = $"{log.Timestamp.DateTime.ToString("HH:mm:ss")}: {log.MessageTemplate}\n{this.logTextBox.Text}";
}
}
I have a form that has a button to get a method executed in another class.
Code on the form:
public delegate void CustomPreviewCreate();
public static event CustomPreviewCreate CustomPreviewCreate_Do;
private void CreatePreview()
{
if (CustomPreviewCreate_Do !=null)
{
CustomPreviewCreate_Do();
}
}
This event then gets handled in another class. What I would like to achieve is that I can feed back to the form some form of return value if the method correctly executed.
What I tried so far does not get me the result.
Here is the code:
public void Initialize()
{
SubAsstViewPartControl.CustomPreviewCreate_Do += SubAsstViewPartControl_CustomPreviewCreate_Do;
// this gives me a the compiler error that the return type is wrong
}
private bool SubAsstViewPartControl_CustomPreviewCreate_Do()
{
// do stuff
return false;
}
Is there any direct way to return value from an event handler or I need to use a separate static field to store the event result in?
Update:
Per #Jon's comment, which seemed the simplest to me, I added an answer below demonstrating the simplest approach.
The common approach is to encapsulate your value in the type of EventArgs your event expects. For example, the Framework's CancelEventArgs contains a settable bool Cancel property, allowing each CancelEventHandler to assign a value. The sender can then read the property after the event has been invoked. You could also use a container-like EventArgs class if you want to collect separate values from individual event handlers. For example:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
public class SingleValueEventArgs : EventArgs
{
public int Value { get; set; }
}
public class MultiValueEventArgs : EventArgs
{
private List<int> _values = new List<int>(); // Private to prevent handlers from messing with each others' values
public IEnumerable<int> Values
{
get { return _values; }
}
public void AddValue(int value) { _values.Add(value); }
}
public class Exposer
{
public event EventHandler<SingleValueEventArgs> WantSingleValue;
public event EventHandler<MultiValueEventArgs> WantMultipleValues;
public void Run()
{
if (WantSingleValue != null)
{
var args = new SingleValueEventArgs();
WantSingleValue(this, args);
Console.WriteLine("Last handler produced " + args.Value.ToString());
}
if (WantMultipleValues != null)
{
var args = new MultiValueEventArgs();
WantMultipleValues(this, args);
foreach (var value in args.Values)
{
Console.WriteLine("A handler produced " + value.ToString());
}
}
}
}
public class Handler
{
private int _value;
public Handler(Exposer exposer, int value)
{
_value = value;
exposer.WantSingleValue += exposer_WantSingleValue;
exposer.WantMultipleValues += exposer_WantMultipleValues;
}
void exposer_WantSingleValue(object sender, SingleValueEventArgs e)
{
Console.WriteLine("Handler assigning " + _value.ToString());
e.Value = _value;
}
void exposer_WantMultipleValues(object sender, MultiValueEventArgs e)
{
Console.WriteLine("Handler adding " + _value.ToString());
e.AddValue(_value);
}
}
class Program
{
static void Main(string[] args)
{
var exposer = new Exposer();
for (var i = 0; i < 5; i++)
{
new Handler(exposer, i);
}
exposer.Run();
}
}
}
Per Jon Skeet's comment, which seemed the simplest to me, the simplest approach seems to be as follows:
public delegate bool CustomPreviewCreate(); // here we declare a return type
public static event CustomPreviewCreate CustomPreviewCreate_Do;
private void CreatePreview()
{
if (CustomPreviewCreate_Do !=null)
{
bool returnval = CustomPreviewCreate_Do();
}
}
And then:
// the method is declared to return the same type
bool SubAsstViewPartControl_CustomPreviewCreate_Do()
{
// do stuff
return true; // return the value of the type declared
}
i'm learning how to use custom events in c#, but i get some errors
i get "An object reference is required for the nonstatic field, method, or property" in the bold words
so i tried following this
but case 1 couldn't be tried 'cause TypeChanged is already a nonstatic method (i think)
in case 2 i get "impossible to acces BicycleType as an instance reference, qualify it as a type"
public class Bicycle
{
public event EventHandler TypeChanged;
private string type;
...
public string BicycleType {
get { return this.type; }
set {
this.type = value;
if (this.TypeChanged != null)
this.TypeChanged( this, new EventArgs() );
}
}
public Bicycle() {}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("heila!");
Bicycle istanza = new Bicycle();
istanza.TypeChanged += new EventHandler(**istanza_TypeChanged**);
istanza.BicycleType = "io";
Console.WriteLine("io");
}
void istanza_TypeChanged(object sender, EventArgs e) {
Console.WriteLine("rofkd");
}
}
the tutorial i followed told me i can use events "as" methods, maybe i'm wrong here?
the code is completely similar to te tutorial code
sorry for my bad english and thanks in advance
As you are registering the event from the main method, which is static, the event handler (istanza_TypeChanged) has to be made static too.
You problem is that Main is static and can therefore not access nonstatic members of the class Program. However you try to access istanza_TypeChanged. That is what is causing your exception.
You have to make istanza_TypeChanged static too to solve the issue
class Program
{
static void Main(string[] args)
{
Console.WriteLine("heila!");
Bicycle istanza = new Bicycle();
istanza.TypeChanged += new EventHandler(**istanza_TypeChanged**);
istanza.BicycleType = "io";
Console.WriteLine("io");
}
static void istanza_TypeChanged(object sender, EventArgs e)
{
Console.WriteLine("rofkd");
}
}
Register the event from a non-static context or change your event to be static.
Change istanza_TypeChanged to the following:
private static void istanza_TypeChanged(object sender, EventArgs e)
{
Console.WriteLine("rofkd");
}
The following fired the event for me:
public class Bicycle
{
public event EventHandler TypeChanged;
private string type;
public string BicycleType
{
get { return this.type; }
set
{
this.type = value;
if (this.TypeChanged != null)
this.TypeChanged(this, new EventArgs());
}
}
public Bicycle()
{
}
private class Program
{
private static void Main(string[] args)
{
Console.WriteLine("heila!");
Bicycle istanza = new Bicycle();
istanza.TypeChanged += istanza_TypeChanged;
istanza.BicycleType = "io";
Console.WriteLine("io");
}
private static void istanza_TypeChanged(object sender, EventArgs e)
{
Console.WriteLine("rofkd");
}
}
}
I have 2 classes: MyForm and Database
In MyForm I have a method to change a label text to show error:
public void printError(string text){
label1.Text = text;
}
My Database class needs to access that method too, so I make it static:
public static void printError(MyForm form, string text){
form.label1.Text = text;
}
Now the problem is, how do I call that method from Database class?
This question I found said that I need to pass MyForm into Database's contructor like this:
class MyForm : Form{
Database db;
public Form(){
db = new Database(this);
}
}
class Database{
MyForm form;
public Database(MyForm f){
form = f;
}
...
//then I can access the printError like this
MyForm.printError(form, "You got error");
}
I tried that and it freezes the form. Any other solution?
Thanks
Here is a very simple example of how you can achieve this without your data layer knowing about your UI:
class MyForm : Form
{
Database db;
public Form()
{
db = new Database(this);
}
public void DoSomething()
{
var errors = db.Login("", "");
if (errors.Any())
label1.Text = errors.First(); // Or you can display all all of them
}
}
class Database
{
public List<string> Login(string username, string password)
{
var errors = new List<string>();
if (string.IsNullOrEmpty(username))
errors.Add("Username is required");
if (string.IsNullOrEmpty(password))
errors.Add("Password is required");
[...]
return errors;
}
}
Like #Matthew Ferreira and others have stated the design is not idea, but here's something to get you started.
class MyForm : Form
{
public void SomeMethod()
{
var dataAccess = new Repository();
dataAccess.ExecuteQuery();
if (dataAccess.Exceptions.Any())
{
// display your error messages
form.label1.Text = dataAccess.Exceptions.Select(x => x.ToString());
}
}
}
class Repository
{
private readonly HashSet<Exception> _exceptions = new HashSet<Exception>();
public IEnumerable<Exception> Exceptions
{
get { return _exceptions; }
}
public int ExecuteQuery()
{
var numberOfRecordsAffected = 0;
try
{
// do something
}
catch (Exception ex)
{
// normall catching exceptions is a bad idea
// and you should really catch the exception at the
// layer best equiped to deal with it
_exceptions.Add(ex);
}
// but, for the purpose of this example we might want to add some logic to try the query on another database ????
try
{
// do something
}
catch (Exception ex)
{
_exceptions.Add(ex);
}
return numberOfRecordsAffected;
}
}
You need to look up "seperation of concerns". Its really bad to mix your UI code with you Database Access Layer (DAL). Better to bind the UI to business objects that are populated via a DAL.
To let the UI know about an error you could simply use a delegate.
namespace OperationErrorDelegate
{
public delegate void OperationErrorHandler(Exception ex);
public class DAL
{
public event OperationErrorHandler ReportError;
public void DoDALOperationThatCausesError()
{
try
{
int i = 1;
int j = 0;
int k = i/j;
}
catch (Exception ex)
{
ReportError(ex);
}
}
}
}
Add this code to the form:
using System ;
using System.Windows.Forms;
namespace OperationErrorDelegate
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
DAL DAL = new DAL();
DAL.ReportError += new OperationErrorHandler(DAL_OperationErrorProgress);
DAL.DoDALOperationThatCausesError();
}
private void DAL_OperationErrorProgress(Exception ex)
{
label1.Text = ex.Message;
}
}
}
Assuming the OP's requirement is to display an error message in a label, when the credentials are wrong:
private void btn_login_Click(object sender, EventArgs e)
{
MySqlConnection con = new MySqlConnection("server=localhost;uid=root;password=abc;database=mydb");
MySqlCommand cmd = new MySqlCommand("select * from emp where name='" + textBox1.Text + "'and pwd='" + textBox2.Text + "'",con);
con.Open();
MySqlDataReader dr = cmd.ExecuteReader();
if (dr.Read())
{ //successful
//navigate to next page or whatever you want
}
else
Label1.Text("Invalid userid or password");
con.Close();
}
And if you need error message for wrong data type (the user input string but the database column is Integer), then use validations at client side. You dont need to do it at backend, since that will be a burden.
You can use regular expressions for that in the button_click itself.
I am trying to make a Progress Bar on a WinForm work by making two methods public and then calling the methods from another class (see my code example below). But no luck, the bar does not move.
Here are the two classes:
namespace GP_Avantis_Integration
{
//Class B
public partial class GP_Avantis_Integration_Window : Form
{
public GP_Avantis_Integration_Window()
{
InitializeComponent();
}
DataSet ds = new DataSet();
SqlDataAdapter sqlda = new SqlDataAdapter();
SqlCommand sqlcomm = new SqlCommand();
public static int recno;
public void button1_Click(object sender, EventArgs e)
{
try
{
//Fetch data into memory
//Fill in Header table
//Fill in Line table
//Cleaning open connection
//Creating relationship in the Dataset between Header and Line table
// Instantiating and Crearintg Header and Line source
//Binding the Header source to the Header table
//Binding the Line source to the relationship
}
catch (ApplicationException ae)
{
}
finally
{
}
}
public void button2_Click(object sender, EventArgs e)
{
try
{
//Calling CreateJE Class
//Class Method ProcessData
CreateJE JE = new CreateJE(); --------> Calls the Class B
JE.ProcessData(ds);
MessageBox.Show("Complete!");
}
catch (ApplicationException ae)
{
}
finally
{
}
}
public void progress_Bar_setup()
{
progressBar1.Minimum = 0;
progressBar1.Maximum = CreateJE.max;
}
public void progressBar_updates(int recno)
{
progressBar1.Value = recno;
progressBar1.Update();
}
}
// Class B
class CreateJE
{
static public int max;
public void ProcessData (DataSet ds)
{
//Create an eConnect Trx type object
//POPTransactionType po = new POPTransactionType();
// ***** PO Header and Line
int ln;
ln = 0;
//Setting up ProgressBar
int recno = 1;
max = ds.Tables[0].Rows.Count;
GP_Avantis_Integration_Window w = new GP_Avantis_Integration_Window();
w.progress_Bar_setup();
// Create an eConnect PO Header node object
// Create an array for lineitems
foreach (DataRow dtrHDR in ds.Tables["Header"].Rows)
{
//ProgressBar Updates
w.progressBar_updates(recno);
//Instantiating GetJE object
//Retrieves the next JE from GP
//Create an eConnect PO Header node object
//Add the header node to the trx type object
ln = 0;
foreach (DataRow dtrLine in dtrHDR.GetChildRows("HdrLine"))
{
// Populate the elements of the taPoLIne_ItemsTaPOLine XML node
//Avantis Inv Trx Key
// Avantis GL Trx Type
//Add POLine to an Array
ln ++;
}
// Add the header node to the trx type object
// Add the lineitem node to the trx type object
// ***** Process information only
// Create an eConnect document object
// Create a file on the HD
// Serialize using the XmlTextWriter to the file
// Call the eConnectMethods
// Separate Class
// Instantiating the object for eConnectMethods class
// Passing last JRNENTRY retreived using the GetJE class
// so if there is an error on the eConnectEntry Method of eConnectMethods Class
// I can pass the last JE number back to GP
recno++;
}
}
}
}
It's hard to say what is the problem. May be the max value doesn't get initialized, may be the update() method doesn't redraw the progress bar.
One more thing: may be it's better to use the BackgroundWorker, so your UI won't lock? Here's an example: http://www.codeproject.com/Tips/83317/BackgroundWorker-and-ProgressBar-demo
I think you need to invoke the updating with render priority, like this:
public void progressbar_updates(int recno)
{
Dispatcher.Invoke(new Action(() =>
{
progressbar1.Value += recno;
progressbar1.UpdateLayout();
}), DispatcherPriority.Render);
}
Implement a backgroundWorker and use backgroundworker.ReportProgress to update the progressbar. It will then implement your code in one thread and the UI in another thead. Then the form will be responding to updates. Just be cautious when you read and write to the UI (because it is in another thead) Read the UI over when you start the Backgroundworker and write in ReportProgress.
C# Winform ProgressBar and BackgroundWorker
Use backgrounworker.
This is the best example
Click here for BackGroundWorker
I implemented a "Worker Form" which displays progress of background tasks.
Here's my code (WorkerForm)
#region Form
private void frmLoader_Load(object sender, EventArgs e)
{
if (OnExecute != null)
{
OnExecute(this, null);
}
}
#endregion
#region "Public"
public void OnInitialize(object sender, EventArgs<String, int> e)
{
if (pbLoader.InvokeRequired)
{
pbLoader.Invoke(new Action<object, EventArgs<String, int>>(OnInitialize), new Object[] { sender, e });
}
else
{
lblDynamicText.Text = e.Param1;
pbLoader.Step = 1;
pbLoader.Minimum = 1;
pbLoader.Value = 1;
pbLoader.Maximum = e.Param2;
}
}
public void OnCreate(object sender, EventArgs<String> e)
{
if (lblDynamicText.InvokeRequired)
{
lblDynamicText.Invoke(new Action<object, EventArgs<String>>(OnCreate), new Object[] { sender, e });
}
else
{
lblDynamicText.Text = e.Param1;
pbLoader.PerformStep();
}
}
public void OnFinished(object sender, EventArgs<String> e)
{
if (lblDynamicText.InvokeRequired)
{
lblDynamicText.Invoke(new Action<object, EventArgs<String>>(OnFinished), new Object[] { sender, e });
}
else
{
lblDynamicText.Text = e.Param1;
}
}
In my presenter (I'm using a ModelViewPresenter architecture) I inject both a reference to the Winform and the actual worker class.
internal WorkerPresenter(IWorkerView WorkerView,IWorker ConcreteWorker)
{
this.WorkerView = WorkerView;
this.ConcreteWorker = ConcreteWorker;
WorkerView.OnExecute += StartExecute;
}
StartExecute is being raised during the Form Load Event of my Worker Form.
private void StartExecute(Object sender, EventArgs e)
{
ConcreteWorker.OnCreate += Create;
ConcreteWorker.OnFinish += Finished;
ConcreteWorker.OnInitialize += Initialize;
var task = new Task<bool>(ConcreteWorker.Execute);
task.Start();
task.ContinueWith(c_task =>
{
((frmWorker)WorkerView).DialogResult = System.Windows.Forms.DialogResult.OK;
});
}
IWorker interface:
public interface IWorker {
event EventHandler<EventArgs<String>> OnCreate;
event EventHandler<EventArgs<String>> OnFinish;
event EventHandler<EventArgs<String, int>> OnInitialize;
bool Execute();
}
Concrete Worker: OnInitialize => Set a text and the amount of steps in your progress bar. OnCreate => Increments the step counter by one and sets another text.
OnFinish => displays a text and closes the Worker form again.