I'm writing an application to read and analyze some logs my company software uses. There are multiple types of logs, but let's take only two for the purpose of describing my problem. Namely, logs of TypeA
and TypeB
. I have designed one class to hold a single line of log data, named LogLine
which looks like below.
public class LogLine
{
public long LineNum { get; set; }
public string Msg { get; set; }
}
So here's my problem/requirement.
In my main ViewModel
, I'd like to read logs of each type only once when the application loads. Read TypeA
logs one time, and store in an ObservableCollection
of LogLine
instances, do the same for TypeB
. Then depending on my choice the DataGrid
displays logs from one type, and if I click a button at any time, the same DataGrid
should display logs from the other type. Note that my logs data doesn't change, I simply want to display my choice of logs.
For this I created three classes, namely, ControllerMain
, ControllerA
, and ControllerB
. The last two derive from the former like so:
public class ControllerMain
{
public ControllerMain()
{
LogLineList = new ObservableCollection<LogLine>();
}
private ObservableCollection<LogLine> logLineList;
public ObservableCollection<LogLine> LogLineList
{
get { return logLineList; }
set { logLineList = value; }
}
}
public class ControllerA : ControllerMain
{
public ControllerA() { }
// More stuff here
}
public class ControllerB : ControllerMain
{
public ControllerB() { }
// More stuff here
}
As you can guess ControllerA
is intended to hold logs of TypeA
, and associated properties and methods unique to those logs. Same goes for TypeB
logs.
In my ViewModel
, I have instances of each of the classes above like so, and at application load I read log data and store in appropriate class object.
public ControllerMain COMMON_LOG { get; set; }
public ControllerA A_LOG { get; set; }
public ControllerB B_LOG { get; set; }
public ViewModelMain()
{
isAType = true;
ClickCommand = new CustomCommand(ClickCmd, CanClickCmd);
A_LOG = new ControllerA
{
// This simulates reading logs from files - done only once
LogLineList = DataService.GetAData()
};
B_LOG = new ControllerB
{
// This simulates reading logs from files - done only once
LogLineList = DataService.GetBData()
};
// This simulates switching to already loaded logs.
// When I do this the log lines don't change, but I want to refresh the datagrid and display correct info.
LoadAppropriateLog();
}
private void LoadAppropriateLog()
{
if (isAType)
{
COMMON_LOG = A_LOG;
isAType = false;
}
else
{
COMMON_LOG = B_LOG;
isAType = true;
}
}
My View
binds to the COMMON_LOG
instance like below:
<DataGrid Grid.Row="0" Margin="5"
Name="dgLogs"
AutoGenerateColumns="False" SelectionUnit="CellOrRowHeader"
ItemsSource="{Binding COMMON_LOG.LogLineList}">
Then at the click of a button, I call the above LoadAppropriateLog()
method, so it will simply assign the instance of appropriate type to COMMON_LOG
which is the instance I've used to data bind.
The problem is that when I do so, since the actual data in each instance's LogLineList
doesn't change, the DataGrid
doesn't automatically update to reflect my choice of logs.
Is there a way to manually refresh the DataGrid
from my ViewModel
after every time I switch the type of log?
If you'd like to run the project and see, here's a download link:
If you're binding to a property of a class in XAML, either
INotifyPropertyChanged
and the property should raise PropertyChanged
in its setter. In your case, you're changing the value of COMMON_LOG
, and you're never changing the value of its LogLineList
.
tl;dr: So your main viewmodel needs to implement INotifyPropertyChanged
, and raise PropertyChanged
in the setter for COMMON_LOG
. Anything that doesn't do those things isn't a viewmodel.
LogLineList
being an ObservableCollection
won't accomplish anything: What that class does is raise notifications when items are added, removed or replaced. That doesn't happen at any time after the binding sees it. Those instances of ObservableCollection
don't even know that the main viewmodel even exists, so they certainly can't be expected to raise notification events when its properties change. Nor should they: Everybody is responsible for exactly his own notifications.
In fact, if you've made a design decision that those collections never change after initialization, use ReadOnlyCollection
instead of ObservableCollection
. Creating one is easy: Call List<T>.AsReadOnly<T>()
. For any IEnumerable<T>
, just call e.ToList().AsReadOnly()
. ObservableCollection
signals "you can add stuff to this". But nobody should. So don't give them ideas.