So I have form that show my Application Log.
This is my Log model:
public class LogEntry : IComparable<LogEntry>
{
public string DateTime { get; set; }
public int Index { get; set; }
public string Source { get; set; }
public Level Level { get; set; }
public string Message { get; set; }
public int CompareTo(LogEntry other)
{
return DateTime.CompareTo(other.DateTime);
}
}
public enum Level
{
All = 0,
Debug,
Info,
Warn,
Error,
Fatal,
Off
}
Log Helper
This is my LogHelper class that add the current LogEvent according the level that the user selected:
public static class LogHelper
{
public static ObservableCollection<LogEntry> LogEntries { get; set; }
public static bool AddLogToList { get; set; }
private static int _level;
private static int _index;
private static string _formatPattern = "yyyy-MM-dd HH:mm:ss,fff";
public static void SetLevel(Level level)
{
_level = (int)level;
}
public static void AddLog(Level level, string message, string className, string methodName)
{
if (LogEntries == null)
LogEntries = new ObservableCollection<LogEntry>();
if (AddLogToList)
{
int levelValue = (int)level;
if (levelValue >= _level)
{
Application.Current.Dispatcher.Invoke(new Action(() =>
{
if (LogEntries.Count == 1000)
LogEntries.RemoveAt(LogEntries.Count - 1);
LogEntry logEntry = new LogEntry()
{
DateTime = DateTime.Now.ToString(_formatPattern),
Index = _index++,
Level = level,
Source = className + "\\" + methodName,
Message = message.Trim()
};
LogEntries.Insert(0, logEntry);
}));
}
}
}
}
So I am add LogEvent into my list that contains up ti 1000 entries.
Now I want to be able to filter and show my only the relevant LogEvent Level.
So I added ComboBox with all my LogEvent levels and subscribe to its SelectionChanged event:
private void cbLogLevel_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
int index = cbLogLevel.SelectedIndex;
LogHelper.SetLevel((Level)index);
lvLogger.ItemsSource = LogHelper.LogEntries.Where(m => m.Level == (Level)index).ToList();
}
So after this SelectionChanged event I can see the relevant LogEvent level but my only issue is the new LogEvent not shows.
Maybe I need kind of refresh to my collection or something else ?
You are creating a new List<LogEntry> and setting the ItemsSource property to this one in your event handler. This means that lvLogger will no longer be connected to the ObservableCollection.
Instead of resetting the ItemsSource, you could filter the view:
private void cbLogLevel_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
int index = cbLogLevel.SelectedIndex;
Level level = (Level)index;
LogHelper.SetLevel(level);
var collectionView = CollectionViewSource.GetDefaultView(lvLogger.ItemsSource);
collectionView.Filter = x =>
{
LogEntry logEntry = x as LogEntry;
return logEntry != null && logEntry.Level == level;
};
}
Related
How to notify the modification of an interface that changes in a DLL on the bindings side.
To explain:
Dll code is not editable:
public interface IPlayer
{
int Id { get; }
string Name { get; }
Settings Settings { get; }
PlayerCategory Category { get; }
}
public class TennisPlayer: IPlayer
{
public virtual int Id { get; }
public virtual string Name { get; set; }
public Tennisman(int id, string name)
{
Id = id;
Name = name;
}
public Settings Settings { get; set; }
public PlayerCategory Category { get; set; }
}
My code:
public partial class PlayerItem : NotifyUserControl
{
private DispatcherTimer timer = new DispatcherTimer();
public static readonly DependencyProperty PlayerProperty =
DependencyProperty.Register("Player", typeof(IPlayer),
typeof(PlayerItem),
new PropertyMetadata(null, OnCaptionPropertyChanged));
public IPlayer Player
{
get { return (IPlayer)GetValue(PlayerProperty); }
set
{
SetValue(PlayerProperty, value);
}
}
public string PlayerName
{
get => Player != null ? Player.Name : "";
set => OnPropertyChanged();
}
public PlayerItem()
{
InitializeComponent();
timer.Interval = new TimeSpan(0, 0, 4);
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
OnPropertyChanged(nameof(PlayerName));
}
The external dll gives me player classes( for example: tennisman, footballer...) based on the same interface.
But I don't know it and I don't have to do class by class.
I must be missing something huge, but I managed to find nothing in my research.
May be this could be accomplished using a trigger and an audit log table in SQL server. Or perhaps it could be accomplished by overriding the SaveChanges() method in Entity Framework. My concern is how to write the code to get it done and which one will be efficient. Can anybody help me?
If you log changes through the code, alongside data, you can add additional information to the audit log such as IP, user info, client info, ... and it is really helpful. The downside would be if someone changes data directly via the database you cannot find out what data has changed and the performance of logging data change by audit log table is better. If you just need to capture data change and don't need to find out who and from where data has changed choose the database approach.
Here is an implementation to capture data changes by EF Core:
public interface IAuditableEntity
{
}
public class AuditLogEntity
{
public int Id { get; set; }
public string UserName { get; set; }
public DateTime CreateDate { get; set; }
public ChangeType ChangeType { get; set; }
public string EntityId { get; set; }
public string EntityName { get; set; }
public IEnumerable<EntityPropertyChange> Changes { get; set; }
}
public class EntityPropertyChange
{
public string PropertyName { get; set; }
public string OldValue { get; set; }
public string NewValue { get; set; }
}
public enum ChangeType
{
Add = 1,
Edit = 2,
Remove = 3
}
In DbContext:
public class RegistryDbContext : DbContext
{
private readonly IHttpContextAccessor _contextAccessor;
public RegistryDbContext(DbContextOptions<RegistryDbContext> options, IHttpContextAccessor contextAccessor) :
base(options)
{
_contextAccessor = contextAccessor;
}
public DbSet<AuditLogEntity> AuditLogs { get; set; }
public override int SaveChanges()
{
CaptureChanges();
return base.SaveChanges();
}
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
CaptureChanges();
return await base.SaveChangesAsync(cancellationToken);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AuditLogEntity>().Property(p => p.UserId).IsUnicode(false).HasMaxLength(36).IsRequired(false);
modelBuilder.Entity<AuditLogEntity>().Property(p => p.EntityId).IsUnicode(false).HasMaxLength(36).IsRequired();
modelBuilder.Entity<AuditLogEntity>().Property(p => p.EntityName).IsUnicode(false).HasMaxLength(256).IsRequired(false);
builder.Ignore(p => p.Changes);
modelBuilder.Entity<AuditLogEntity>()
.Property(p => p.Changes).IsUnicode().HasMaxLength(int.MaxValue).IsRequired(false)
.HasConversion(
changes => JsonConvert.SerializeObject(changes, Formatting.None, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Include,
TypeNameHandling = TypeNameHandling.Auto
}),
changes => JsonConvert.DeserializeObject<IList<EntityPropertyChange>>(changes, new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Include,
TypeNameHandling = TypeNameHandling.Auto
}));
}
private void CaptureChanges()
{
var changes = ChangeTracker
.Entries<IAuditableEntity>()
.Where(e =>
e.State == EntityState.Added ||
e.State == EntityState.Modified ||
e.State == EntityState.Deleted)
.Select(GetAuditLogItems)
.ToList();
AuditLogs.AddRange(changes);
}
private AuditLogEntity GetAuditLogItems(EntityEntry entry)
{
var auditEntity = new AuditLogEntity
{
CreateDate = DateTime.Now,
EntityId = entry.Properties.FirstOrDefault(f => f.Metadata.IsPrimaryKey())?.CurrentValue?.ToString(),
EntityName = entry.Metadata.Name,
UserName = _contextAccessor?.HttpContext?.User?.Name.ToString(),
};
switch (entry.State)
{
case EntityState.Added:
auditEntity.ChangeType = ChangeType.Add;
auditEntity.Changes = GetChanges(entry.Properties, e => true).ToList();
foreach (var entityChange in auditEntity.Changes)
entityChange.OldValue = null;
break;
case EntityState.Modified:
auditEntity.ChangeType = ChangeType.Edit;
auditEntity.Changes = GetChanges(entry.Properties, e => e.IsModified).ToList();
break;
case EntityState.Deleted:
auditEntity.ChangeType = ChangeType.Remove;
break;
}
return auditEntity;
}
private IEnumerable<EntityPropertyChange> GetChanges(IEnumerable<PropertyEntry> properties,
Func<PropertyEntry, bool> predicate) => properties
.Where(predicate)
.Select(property =>
new EntityPropertyChange
{
PropertyName = property.Metadata.Name,
OldValue = property.OriginalValue?.ToString(),
NewValue = property.CurrentValue?.ToString()
});
}
I use IAuditableEntity (an empty interface) to mark entities that I want to capture changes.
public class CustomerEntity : IAuditableEntity
{
...
}
You can also use Audit.NET library to capture changes.
I've got a datagrid with a checkbox, name and email.
On CheckboxChecked I want to copy the email into another list or string.
How do I get the specific value from the checked row?
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
object row = lbxCC1.SelectedItem;
int columnIndex = lbxCC1.Columns.Single(c => c.Header.Equals("eMail")).DisplayIndex;
String eMail = (lbxCC1.SelectedCells[columnIndex].Column.GetCellContent(row) as TextBlock).Text;
MessageBox.Show(eMail);
}
Edit (09.09.2016):
Maybe I should show you a bit more code.
public class Person
{
public string Nachname { get; set; }
public string Vorname { get; set; }
public string eMail { get; set; }
public string Abteilung { get; set; }
}
public static class PersonService
{
public static List<Person> ReadFile(string filepath)
{
var lines = File.ReadAllLines(filepath);
var data = from l in lines.Skip(1)
let split = l.Split(';')
select new Person
{
Nachname = split[1],
Vorname = split[2],
eMail = split[31],
Abteilung = split[4],
};
return data.ToList();
}
}
I call it with:
lbxCC1.DataContext = PersonService.ReadFile(#"C:\Test.csv");
As I'm building the columns from code behind, I guess I have to bind them aswell am I right?
Sorry for this, but I'm new to datagrids :-)
I think this might help you:
Dim row As Data.DataRowView = DirectCast([yourDataGrid].SelectedItems(rowIndex), Data.DataRowView)
Then in your CheckBox_Checked Event:
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
MessageBox.Show(row("email"); 'Assuming the column name is email
}
This is IF your values are data-bound to the DataGrid.
So I have the following setup:
PLANNING:
public class Planning : ViewModelBase
{
public Planning()
{
AddNewActivityCommand = new RelayCommand(AddActivity, CanAddActivity);
}
public ObservableCollection<PlanningItem> PlanningItems { get; set; }
public PlanningItem SelectedPlan { get; set; }
#region AddNewActivity
public RelayCommand AddNewActivityCommand { get; private set; }
private bool CanAddActivity()
{
if (!PlanningItems.Any())
{
return true;
}
if (string.IsNullOrEmpty(PlanningItems[PlanningItems.Count - 1].Activities) != true ||
PlanningItems[PlanningItems.Count - 1].DhpRepresentativeSelected != null)
{
return true;
}
return false;
}
private void AddActivity()
{
PlanningItems.Add(new PlanningItem());
AddNewActivityCommand.RaiseCanExecuteChanged();
}
#endregion
}
PLANNING ITEM:
public class PlanningItem : ViewModelBase
{
private string _activity;
public ObservableCollection<OutreachUser> DhpRepresentativeSource
{
get
{
var userSource = new ObservableCollection<OutreachUser>();
using (var context = new Outreach_Entities())
{
var query = from a in context.UserInfoes
join b in context.PersonalInfoes on a.UserIdentity equals b.PersonIdentity
join c in context.PersonalTitles on b.TitleLink equals c.TitleIdentity into cGroup
from c in cGroup.DefaultIfEmpty()
select new OutreachUser
{
PersonLink = a.UserIdentity,
Username = a.Username,
FirstName = b.FirstName,
MiddleInitial = b.MiddleInitial,
LastName = b.LastName
};
foreach (var result in query)
{
userSource.Add(result);
}
return userSource;
}
}
}
public OutreachUser DhpRepresentativeSelected { get; set; }
public DateTime PlanningDate { get; set; }
public TimeSpan PlanningStart { get; set; }
public TimeSpan PlanningEnd { get; set; }
public int PlanningTotalHours { get; set; }
public string Activities
{
get
{
return _activity;
}
set
{
_activity = value;
RaisePropertyChanged(nameof(Activities), "", _activity, true);
}
}
}
I have a ListBox bound to the PlanningItems Observable Collection.
I want to be able to add a new item to the list if the following criteria are met:
The Planning Items Collection is empty.
The last item in the Planning Items Collection has a DhpRepresentativeSelected that is not null.
The last item in the Planning Items Collection has some text in the Activities string.
The first item is easy enough because I call AddNewActivityCommand.RaiseCanExecuteChanged(); after I add a new item from an empty list.
Now I need to call the AddNewActivityCommand.RaiseCanExecuteChanged(); from within the PlanningItem ViewModel, but it does not have access rights to the command.
Clueless pointed me to the answer.
What I did was inside of my Planning ViewModel I created an internal Method that called the AddNewActivityCommand.RaiseCanExecuteChanged() method. I think called that method from within the PlanningItems ViewModel.
Why this work :
public Form1()
{
InitializeComponent();
exCheckedListBox1.DataSource = Profiles;
this.exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", this, "States", true));
}
CheckedBindingList Profiles = new CheckedBindingList();
public int States
{
get
{
return Profiles.States;
}
set
{
Profiles.States = value;
}
}
}
public class CheckedBindingList : List<string>
{
public int States { get; set; }
}
but when change binding to
this.exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", this.Profiles, "States", true));
throw the Exception ?
Thanks all very very very. I try to bind filed from my custom list class that inherit form List.
Exception - Cannot bind to the property or column States on the DataSource.
Parameter name: dataMember
Seems System.Windows.Forms.Binding parameter dataSource cannot be a class that inherits from List< T >
This solves the problem:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Load += new EventHandler(Form1_Load);
}
CheckedBindingList Profiles = new CheckedBindingList();
SomeClass some_class = new SomeClass();
public int States
{
get
{
return Profiles.States;
}
set
{
Profiles.States = value;
}
}
private void Form1_Load(object sender, EventArgs e)
{
exCheckedListBox1.DataSource = Profiles;
exCheckedListBox1.DataBindings.Add(new System.Windows.Forms.Binding("Tag", some_class, "States", true));
}
}
public class CheckedBindingList : List<string>
{
public int States { get; set; }
}
public class SomeClass
{
public int States { get; set; }
}