5
\$\begingroup\$

First off, I am very new to WPF. These are the objects I want to display:

public class IssueWithComments
{
    public Issue Issue { get; set; }

    public IReadOnlyList<IssueComment> Comments { get; set; }

    public IssueWithComments()
    {
        Comments = new List<IssueComment>();
    }
}

// Simplified
public class Issue
{
    public string Body { get; protected set; }
    public Uri HtmlUrl { get; protected set; }
    public IReadOnlyList<Label> Labels { get; protected set; }
    public Milestone Milestone { get; protected set; }
    public int Number { get; protected set; }
    public PullRequest PullRequest { get; protected set; }
    public ItemState State { get; protected set; }
    public string Title { get; protected set; }}
    public Uri Url { get; protected set; }
    public User User { get; protected set; }
}

public class IssueComment
{
    public string Body { get; protected set; }
    public Uri HtmlUrl { get; protected set; }
    public Uri Url { get; protected set; }
    public User User { get; protected set; }
}

The following is my XAML:

Within MainWindow.xaml

  <ScrollViewer DockPanel.Dock="Right" CanContentScroll="True" >
        <StackPanel x:Name="grid" ScrollViewer.VerticalScrollBarVisibility="Auto"/>
    </ScrollViewer>

Within GithubIssue.xaml

 <UserControl
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:IssuesManagment.UI.POC.Utils"
     xmlns:Octokit="clr-namespace:Octokit;assembly=Octokit" x:Class="IssuesManagment.UI.POC.Controls.GithubIssue" 
     xmlns:models="clr-namespace:IssuesManagment;assembly=IssuesManagment.Models"
     mc:Ignorable="d" 
     d:DesignHeight="147" d:DesignWidth="295" BorderBrush="#FFD84B4B" BorderThickness="1" Width="Auto" Height="Auto" Margin="0,0,0,2">
   <UserControl.Resources>
    <local:StringToBrushConverter x:Key="stringToBrushConverter"/>
    <local:IntegerToVisiblityConverter x:Key="intToVisiblityConverter"/>
</UserControl.Resources>
<UserControl.DataContext>
    <models:IssueWithComments/>
</UserControl.DataContext>
<StackPanel>
    <TextBlock>
        <Hyperlink NavigateUri="{Binding Issue.HtmlUrl}" RequestNavigate="Hyperlink_RequestNavigate">
            <TextBlock x:Name="issueTitle" TextWrapping="Wrap" Text="{Binding Issue.Title}" FontWeight="Bold" FontSize="16" />
        </Hyperlink>
    </TextBlock>
    <ItemsControl ItemsSource="{Binding Issue.Labels}" HorizontalAlignment="Left">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate >
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate DataType="Octokit:Label">
                <TextBlock Text="{Binding Name}" Background="{Binding Color, Converter={StaticResource stringToBrushConverter}}" Foreground="White" Padding="2" MaxWidth="100" Margin="0,0,1,0" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    <TextBlock x:Name="issueBody" TextWrapping="Wrap" Text="{Binding Issue.Body}" Margin="2"/>
    <Expander  Visibility="{Binding Comments.Count, Converter={StaticResource intToVisiblityConverter}}" Header="Comments">
        <ItemsControl Name="comments" ItemsSource="{Binding Comments}">
        </ItemsControl>
    </Expander>
</StackPanel>
</UserControl>

GithubIssueComment.xaml

    <UserControl x:Class="IssuesManagment.UI.POC.Controls.GithubIssueComment" xmlns:Octokit="clr-namespace:Octokit;assembly=Octokit"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" d:DesignWidth="300" Width="Auto" Height="Auto">
    <UserControl.DataContext>
        <Octokit:IssueComment/>
    </UserControl.DataContext>
    <StackPanel>
        <TextBlock Text="{Binding Body}" Padding="2" />
        <Label Content="By:"/>
        <TextBlock Text="{Binding User.Login}"/>
    </StackPanel>
</UserControl>

C# Code:

Main.xaml.cs:

private async void ViewIssues_Click(object sender, RoutedEventArgs e)
{
    grid.Children.Clear();
    var apiClient = new Octokit.GitHubClient(new Octokit.ProductHeaderValue("Issue-Managment"));
    if (!string.IsNullOrWhiteSpace(userName.Text) && !string.IsNullOrWhiteSpace(repoName.Text))
    {
        var issuesClient = new IssuesWithCommentsClient(new Octokit.ApiConnection(apiClient.Connection));
        var issues = await issuesClient.GetAllForRepositoryWithComments(userName.Text, repoName.Text);

        GithubIssue x;

        foreach (var item in issues)
        {
            x = new GithubIssue(item);
            grid.Children.Add(x);
        }
    }
    else
    {
        grid.Children.Add(new Controls.Error());
    }
}

GithubIssue.xaml.cs:

public GithubIssue(IssueWithComments issue)
{
    InitializeComponent();
    this.DataContext = issue;

    var commentDisplayers = new List<GithubIssueComment>();
    foreach (var comment in issue.Comments)
    {
        commentDisplayers.Add(new GithubIssueComment(comment));
    }
    comments.ItemsSource = commentDisplayers;
}

Where have I gone very off track, and where does it just need a little improvement?

\$\endgroup\$
4
\$\begingroup\$

GithubIssue.xaml.cs:

public GithubIssue(IssueWithComments issue)
{
    InitializeComponent();
    this.DataContext = issue;

    var commentDisplayers = new List<GithubIssueComment>();
    foreach (var comment in issue.Comments)
    {
        commentDisplayers.Add(new GithubIssueComment(comment));
    }
    comments.ItemsSource = commentDisplayers;
}

This could be simplified with LINQ. How about:

var commentDisplayers = issue.Comments.Select(comment=>new GithubIssueComment(comment)).ToList();

// or
var commentDisplayers = (from comment in issue.Comments select new GithubIssueComment(comment)).ToList();
\$\endgroup\$
  • \$\begingroup\$ Thank you. I was hoping for some more interaction on the question overall, but every drop of advice is appreciated so here is the acceptance. \$\endgroup\$ – Mord Zuber May 3 '15 at 14:23
  • 1
    \$\begingroup\$ Yah, wish I could do more, but I'm new to XAML too and data binding is still something I'm weak in. \$\endgroup\$ – Snowbody May 4 '15 at 1:29
  • \$\begingroup\$ Thank you so much for adding the bounty. That is really nice! \$\endgroup\$ – Mord Zuber May 4 '15 at 1:50
3
+100
\$\begingroup\$

First and foremost, I would recommend that you write the application using the MVVM design pattern for WPF. It allows clean separation of concerns, and improved testability for you non view specific logic.

As for your current approach, apart from the odd improvement here and there, I think the overall design is good, and here is why.


The requirement is simple in that you have a button that should fetch some data from a service and show it in a list. The API you are using (Octokit) gives you an Issue object, that doesn't have any comments on it, so you have correctly created a new model type IssueWithComments.

Next you need 2 things. A service that fetches a collection of IssueWithComments, and a view to consume that service, which again, you already have (MainWindow and IssuesWithCommentsClient).

The code in your ViewIssues_Click() method (should be called ViewIssues_ClickAsync()) is taking the model objects returned from your service layer and mapping them onto GithubIssue - an object that can be easily consumed by the view. This is analogous to to a ViewModel object in MVVM. Your types might then look like

  • IssuesWithCommentsClient => GithubClient : IGithubClient (injected into MainViewModel)
  • IssueWithComments => GithubIssue
  • GithubIssue => GithubIssueViewModel
  • GithubIssueComment => GithubIssueCommentViewModel

Right now, everything is tightly coupled. View logic (XAML) is coupled to ViewModel logic (code behind) is coupled to Model logic (service layer). This makes the code inflexible, hard to test, and brittle. If you don't see this being a problem for you in the future, then stick with that you have (YAGNI!), otherwise take a look at MVVM and dependency injection which should alleviate some of these problems.

\$\endgroup\$
  • \$\begingroup\$ Thanks for your input. I will look over the link you gave as soon as I have the time. What did you mean by this: GithubIssue - an object that can be easily consumed by the view.? GithubIssue is an user control so is it not considered a view already? \$\endgroup\$ – Mord Zuber May 4 '15 at 11:58
  • 2
    \$\begingroup\$ What I mean by this is that when you add obects of type GithubIssue to grid in your MainWindow.xaml.cs, the view engine knows how to render it. If instead you added IssueWithComments (a model) to grid, it wouldn't work, i.e. you first map it onto something that can be consumed by the view. In this case that happens to be another view, in MVVM it would be a ViewModel equivalent. \$\endgroup\$ – Matt Cole May 4 '15 at 12:21
  • \$\begingroup\$ I guess my learning is cut out for me for the next little bit :) \$\endgroup\$ – Mord Zuber May 4 '15 at 12:22
  • 1
    \$\begingroup\$ Admittedly it is rather a steep learning curve. I used MVVM light when I was picking it up. If you install the template projects, it can create an empty MVVM project, something that is not immediately obvious how to do. Good luck! \$\endgroup\$ – Matt Cole May 4 '15 at 12:28
  • 2
    \$\begingroup\$ Good pointers, but I'd point out that ViewIssues_Click() shouldn't just be renamed, it should be replaced by using commands. \$\endgroup\$ – BCdotWEB May 4 '15 at 13:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.