I am new to and am writing a C#/WPF app to learn more about app design and data binding to UI Elements. This app is intended to be a visual tool to help a user see the status of their local Git repos and perform basic operations via a UI (ex: switch branches, roll back to older commits, fetch/pull changes, etc.). I am also planning on incorporating logging to catch issues if they should ever occur.
My app is currently using two classes; gitRepo which is the top-level class and contains all the general details of a Git repo (ex: local repo location, repo URL, current branch, current commit hash, etc.), and repoHist which contains three ObservableCollections (commit hash, commit type, and commit message) and is used by gitRepo.
So far I have been able to get my UI to load and display the Git repo details based on the user's selection, and I have been able to update the UI if my user loads a new repo. I even have a combo box which loads and lists all available repo branches (still cannot get this to automatically select the first item when there is a change, but that is a different topic). When I try displaying my commit history details from the repoHist class, my UI does not load the data into the multi-column list box. I have ran the code and confirmed my repoHist object populates all three ObservableCollections with the repo data. I have also implemented INotifyPropertyChange so my UI updates according to changes (to my understanding).
I've reviewed a few postings to try and resolve this myself, but I've had no luck even though the OPs are resolving a similar issue: windows store app Listview Binding C# WPF binding collection to the listview Bind an ObservableCollection to a ListView WPF - Bind list to listview
Adding my current code to hopefully shed some light, I am not following MVVM since I am still new to C# dev.
repoHist.cs:
internal class repoHist : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
...
private ObservableCollection<string> _commitHash = new ObservableCollection<string>();
public ObservableCollection<string> commitHash
{
get { return _commitHash; }
}
private ObservableCollection<string> _commitType = new ObservableCollection<string>();
public ObservableCollection<string> commitType
{
get { return _commitType; }
}
private ObservableCollection<string> _commitMessage = new ObservableCollection<string>();
public ObservableCollection<string> commitMessage
{
get { return _commitMessage; }
...
public void AddCommitDets(string hash, string type, string msg)
{
commitHash.Add(hash ?? "");
commitType.Add(type ?? "");
commitMessage.Add(msg ?? "");
return;
}
public void ClearCommitDets()
{
commitHash.Clear();
commitType.Clear();
commitMessage.Clear();
return;
}
}
gitRepo.cs:
internal class gitRepo : INotifyPropertyChanged
{
...
private ObservableCollection<string> _repoBranches = new ObservableCollection<string>();
public ObservableCollection<string> repoBranches
{
get { return _repoBranches; }
}
private repoHist _repoHistory = new repoHist();
public repoHist repoHistory
{
get { return _repoHistory; }
}
...
public void runGitCommitHistory(string repoLoc)
{
string gitCommand = "log -10 --oneline";
repoHistory.ClearCommitDets();
(string stdErrStr, string stdOutStr, int exitCode) = gitCommandHandler(repoLoc, gitCommand);
if (exitCode == 0)
{
Regex commitHistParser = new Regex(@"(?'hash'\w{7})\s(?'ignore'\(.*\))?\s(?'type'\w+)\:\s(?'message'.*)|(?'hash'\w{7})\s(?'type'[^\(\)]\w+[^\(\)])\:\s(?'message'.*)");
using (StringReader reader = new StringReader(stdOutStr))
{
string branchDets;
string hash;
string type;
string message;
while ((branchDets = reader.ReadLine()) != null)
{
if (commitHistParser.IsMatch(branchDets))
{
var matches = commitHistParser.Match(branchDets);
hash = matches.Groups["hash"].Value;
type = matches.Groups["type"].Value;
message = matches.Groups["message"].Value;
repoHistory.AddCommitDets(hash, type, message);
}
else { continue; }
}
}
OnPropertyChanged();
}
else
{
OnPropertyChanged();
return;
}
}
public void setGitRepoDetails(string repoLoc)
{
if (this.checkIsGitRepo(repoLoc))
{
this.repoPath = repoLoc;
this.repoName = runGitName(repoLoc);
this.repoURL = runGitURL(repoLoc);
this.currBranchName = runGitCurrBranch(repoLoc);
this.currCommitHash = runGitCurrCommitHash(repoLoc);
this.currCommitMessage = runGitCurrCommitMsg(repoLoc);
this.runGitBranchList(repoLoc);
this.runGitCommitHistory(repoLoc);
}
else
{
this.repoPath = "Invalid Choice";
this.repoName = "Invalid Choice";
this.repoURL = "Invalid Choice";
this.currBranchName = "Invalid Choice";
this.currCommitHash = "Invalid Choice";
this.currCommitMessage = "Invalid Choice";
}
return;
}
}
related code-behind:
public partial class MainWindow : Window
{
private gitRepo currRepo = new gitRepo();
public MainWindow()
{
InitializeComponent();
grd_repoDets.DataContext = currRepo; // Bind all text labels in repo details grid to repo obj
grd_repoConfig.DataContext = currRepo; // Bind branches drop down and repo history multi-column list box to repo obj
}
private void mnuOpen_Repo(object sender, RoutedEventArgs e)
{
CommonOpenFileDialog gitRepoDir = new CommonOpenFileDialog();
gitRepoDir.InitialDirectory = "C:\\sandbox\\";
gitRepoDir.IsFolderPicker = true;
if (gitRepoDir.ShowDialog() == CommonFileDialogResult.Ok)
{
currRepo.setGitRepoDetails(gitRepoDir.FileName);
MessageBox.Show("You selected: " + gitRepoDir.FileName);
}
}
}
XAML:
...
<Grid x:Name="grd_repoConfig" Grid.Row="3">
...
<StackPanel Grid.Column="1" Orientation="Vertical" Margin="5,5,5,5">
<ComboBox x:Name="dd_branchList" ItemsSource="{Binding Path=repoBranches}" Width="250" SelectedIndex="0" />
</StackPanel>
...
<StackPanel Grid.Column="3" Orientation="Vertical" Margin="5,5,5,5">
<ListView x:Name="lb_commitList" ItemsSource="{Binding Path=repoHistory}" Width="750" Height="400">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=commitHash}" Header="CommitHash" Width="250" />
</GridView>
</ListView.View>
</ListView>
</StackPanel>
I've also added the OnPropertyChanged() call to the two methods in repoHist.cs, set a direct data binding to my ListView object lb_commitList.ItemsSource = currRepo; / lb_commitList.DataContext = currRepo; / lb_commitList.ItemsSource = currRepo.repoHistory; / lb_commitList.DataContext = currRepo.repoHistory; /
(ItemsSource binding returns conversion errors), and expanded the bindings with no luck:
<GridViewColumn DisplayMemberBinding="{Binding Path=repoHistory.commitHash}" Header="CommitHash" Width="250" />
If I bind my ListView to the branch list like I do my Combo Box, I get empty rows which correspond to the same number of branches available for my test repos:
<ListView x:Name="lb_commitList" ItemsSource="{Binding Path=repoBranches}" Width="750" Height="400">
<ListView.View>
<GridView>
<GridViewColumn Header="CommitHash" Width="250" />
</GridView>
</ListView.View>
</ListView>
Branch list using above binding definition
UPDATE
After toying with this, I found that changing my ListView so the ItemsSource assignment points directly at one of the ObservableCollections will display the data I expect.
<ListView x:Name="lb_commitList" ItemsSource="{Binding Path=repoHistory.commitHash}" Width="750" Height="400">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding}" Header="CommitHash" Width="250" />
</GridView>
</ListView.View>
</ListView>
With this, I updated my repoHist class so it contains 3 strings (commitHash, commitType, and commitMessage):
private string _commitHash;
public string commitHash
{
get { return _commitHash; }
set { _commitHash = value; }
}
private string _commitType;
public string commitType
{
get { return _commitType; }
set { _commitType = value; }
}
private string _commitMessage;
public string commitMessage
{
get { return _commitMessage; }
set { _commitMessage = value; }
}
I then updated my gitRepo class so my repoHistory is now a single ObservableCollection which gets populated with repoHist objects:
private ObservableCollection<repoHist> _repoHistory = new ObservableCollection<repoHist>();
public ObservableCollection<repoHist> repoHistory
{
get { return _repoHistory; }
}
...
public void runGitCommitHistory(string repoLoc)
{
...
repoHist repoCommitDetail = new repoHist(hash, type, message);
repoHistory.Add(repoCommitDetail);
...
}
Lastly, I updated my ListView XAML so my ItemsSource is my new repoHistory ObservableCollection and my columns display each value from repoHist:
<ListView x:Name="lb_commitList" ItemsSource="{Binding Path=repoHistory}" Width="750" Height="400">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Path=commitHash}" Header="Commit Hash" Width="100" />
<GridViewColumn DisplayMemberBinding="{Binding Path=commitType}" Header="Commit Tyep" Width="100" />
<GridViewColumn DisplayMemberBinding="{Binding Path=commitMessage}" Header="Commit Message" Width="550" />
</GridView>
</ListView.View>
</ListView>
CAN ANYONE EXPLAIN WHY THIS IS? I have a feeling it may be because 1) a ListView cannot directly handle my original implementation of repoHist since it was 3 separate ObservableCollections and 2) it has no way of keeping the index being accessed for each ObservableCollection in sync, so the rows would not display in the way I wanted.