1
\$\begingroup\$

I know I should use mvvm, but just starting with WPF, here is my code:

MainWindow.xaml

<Window x:Class="EasyUpdater.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    mc:Ignorable="d"
    Title="EasyUpdater" Height="550" Width="525" ResizeMode="NoResize">
<Grid Background="Linen">
    <Button x:Name="ButtonOld" Content="..." HorizontalAlignment="Left" Margin="149,35,0,0" VerticalAlignment="Top" Width="25" Click="button_ClickOld"/>
    <TextBox x:Name="TextBoxOld" HorizontalAlignment="Left" Height="23" Margin="24,35,0,0" Text="" VerticalAlignment="Top" Width="120" IsReadOnly="True"/>
    <Button x:Name="ButtonNew" Content="..." HorizontalAlignment="Left" Margin="447,35,0,0" VerticalAlignment="Top" Width="25" Click="button_ClickNew"/>
    <TextBox x:Name="TextBoxNew" HorizontalAlignment="Left" Height="23" Margin="322,35,0,0" Text="" VerticalAlignment="Top" Width="120" IsReadOnly="True"/>
    <Button x:Name="ButtonUpdate" Content="UPDATE" HorizontalAlignment="Left" Margin="120,240,0,0" VerticalAlignment="Top" Width="244" Height="70" Click="buttonUpdate_Click" FontSize="25">
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Text.Length, ElementName=TextBoxOld, UpdateSourceTrigger=PropertyChanged}" Value="0">
                        <Setter Property="IsEnabled" Value="False"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Text.Length, ElementName=TextBoxNew, UpdateSourceTrigger=PropertyChanged}" Value="0">
                        <Setter Property="IsEnabled" Value="False"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>
    <Label x:Name="OldNameLabel" Content="Name" HorizontalAlignment="Left" Margin="24,70,0,0" VerticalAlignment="Top"/>
    <Label x:Name="OldIpLabel" Content="IP" HorizontalAlignment="Left" Margin="24,100,0,0" VerticalAlignment="Top"/>
    <Label x:Name="OldWebpageUriLabel" Content="WebpageUri" HorizontalAlignment="Left" Margin="24,130,0,0" VerticalAlignment="Top" />
    <TextBox x:Name="OldConnectionStringTextBox" Text="ConnectionString" HorizontalAlignment="Left" Margin="24,160,0,0" Width="120"
             VerticalAlignment="Top" Background="Linen" BorderThickness="0" IsReadOnly="True"/>
    <Label x:Name="NewNameLabel" Content="Name" HorizontalAlignment="Left" Margin="322,70,0,0" VerticalAlignment="Top"/>
    <Label x:Name="NewIpLabel" Content="IP" HorizontalAlignment="Left" Margin="322,100,0,0" VerticalAlignment="Top"/>
    <Label x:Name="NewWebpageUriLabel" Content="WebpageUri" HorizontalAlignment="Left" Margin="322,130,0,0" VerticalAlignment="Top"/>
    <TextBox x:Name="NewConnectionStringTextBox" Text="ConnectionString" HorizontalAlignment="Left" Margin="322,160,0,0" Width="120"
             VerticalAlignment="Top" Background="Linen" BorderThickness="0" IsReadOnly="True"/>
    <Label x:Name="ArrowLabel" Content="&lt;-" HorizontalAlignment="Left" Margin="221,14,0,0" VerticalAlignment="Top" FontSize="30" />
    <Label x:Name="OldVersionTextBoxLabel" Content="Old Version:" HorizontalAlignment="Left" Margin="24,4,0,0" VerticalAlignment="Top"/>
    <Label x:Name="NewVersionTextBoxLabel" Content="New Version:" HorizontalAlignment="Left" Margin="325,4,0,0" VerticalAlignment="Top"/>
    <Button x:Name="OldButtonConnectionString" Content="..." HorizontalAlignment="Left" Margin="149,158,0,0" VerticalAlignment="Top" Width="25" Click="button_ClickOldConnection">
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Text.Length, ElementName=OldConnectionStringTextBox, UpdateSourceTrigger=PropertyChanged}" Value="16">
                        <Setter Property="IsEnabled" Value="False"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>
    <Button x:Name="NewButtonConnectionString" Content="..." HorizontalAlignment="Left" Margin="447,158,0,0" VerticalAlignment="Top" Width="25" Click="button_ClickNewConnection">
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Text.Length, ElementName=NewConnectionStringTextBox, UpdateSourceTrigger=PropertyChanged}" Value="16">
                        <Setter Property="IsEnabled" Value="False"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>
    <Button x:Name="StartServiceButton" Content="Start Service" HorizontalAlignment="Left" Margin="10,288,0,0" VerticalAlignment="Top" Width="91" Click="buttonStartService_Click" IsEnabled="False"/>
    <TextBox x:Name="LogsTextBox" Margin="10,315,10,10" TextWrapping="Wrap" Text="Logs:" IsReadOnly="True"/>
    <CheckBox x:Name="StopServiceCheckBox" Content="Stop Service" HorizontalAlignment="Left" Margin="10,267,0,0" VerticalAlignment="Top" FontWeight="Bold"/>
</Grid>

MainWindow.xaml.cs

  public partial class MainWindow
{

    private DirectoryCopy _directoryCopy;
    private ServiceSwitcher _serviceSwitcher;
    private FileSourceResearcher _fileSourceResearcher;
    private Client _client;
    private const string ConfigFilePath = "\\Data.Service.Host.exe.Config";
    private const string IpXmlExpresion = "//client//endpoint[@name = \"IClientsService\"]/@address";
    private const string ConnectionStringXmlExpresion = "//connectionStrings/add[1]/@connectionString";
    private const string WebpageUriXmlExpresion = "//appSettings/add[@key = \"WebpageUri\"]/@value";

    public MainWindow()
    {
        InitializeComponent();
    }

    private void buttonUpdate_Click(object sender, RoutedEventArgs e)
    {
        _serviceSwitcher = new ServiceSwitcher();
        _directoryCopy = new DirectoryCopy();

        if (StopServiceCheckBox.IsChecked != null && StopServiceCheckBox.IsChecked.Value)
            {
                _serviceSwitcher.StopService(_client.Name);
                StartServiceButton.IsEnabled = true;
            }

        ReplaceConnectionStringAndIp();
        _directoryCopy.CopyDirectory(TextBoxNew.Text, TextBoxOld.Text, true);
        LogsTextBox.AppendText("\nUpdate - success");
    }

    private void ReplaceConnectionStringAndIp()
    {
        string readText = File.ReadAllText(TextBoxNew.Text + ConfigFilePath);
        readText = readText.Replace(NewIpLabel.Content.ToString(), OldIpLabel.Content.ToString());
        readText = readText.Replace(NewWebpageUriLabel.Content.ToString(), OldWebpageUriLabel.Content.ToString());
        readText = readText.Replace(NewConnectionStringTextBox.Text, OldConnectionStringTextBox.Text);
        File.WriteAllText(TextBoxNew.Text + ConfigFilePath, readText);

    }

    private void button_ClickOld(object sender, RoutedEventArgs e)
    {
        SelectVerifyAndLog(TextBoxOld, UpdateOldLabel);
    }
    private void button_ClickNew(object sender, RoutedEventArgs e)
    {
        SelectVerifyAndLog(TextBoxNew, UpdateNewLabel);
    }

    void SelectVerifyAndLog(TextBox textBox, Action updateLabel)
    {
        textBox.Text = SelectCatalog();
        if (File.Exists(textBox.Text + ConfigFilePath))
        {
            LogsTextBox.AppendText("\nCorrect folder selected - success");
            GetClientProperty(textBox.Text);
            updateLabel();
        }
        else
        {
           if(textBox.Text != "") LogsTextBox.AppendText("\nWrong folder selected - Config file doesn't exist");
        }
    }


    private string SelectCatalog()
    {
        var folderDialog = new FolderBrowserDialog();

        var result = folderDialog.ShowDialog();
        if (result.ToString() == "OK")
        {
            return folderDialog.SelectedPath;
        }
        return "";
    }
    private void GetClientProperty(string folderDialog)
    {
        var path = folderDialog + ConfigFilePath;

        _fileSourceResearcher = new FileSourceResearcher();

            _client = new Client
            {
                Name = GetFolderName(path),
                Ip = GetIpFromString(_fileSourceResearcher.Search(path, IpXmlExpresion)),
                ConnectionString = _fileSourceResearcher.Search(path, ConnectionStringXmlExpresion),
                WebpageUri = _fileSourceResearcher.Search(path, WebpageUriXmlExpresion)
            };
        LogsTextBox.AppendText("\nDownload version properties - success");
    }

    private void UpdateNewLabel()
    {
        NewNameLabel.Content = _client.Name;
        NewIpLabel.Content = _client.Ip;
        NewConnectionStringTextBox.Text = _client.ConnectionString;
        NewWebpageUriLabel.Content = _client.WebpageUri;
    }
    private void UpdateOldLabel()
    {
        OldNameLabel.Content = _client.Name;
        OldIpLabel.Content = _client.Ip;
        OldConnectionStringTextBox.Text = _client.ConnectionString;
        OldWebpageUriLabel.Content = _client.WebpageUri;
    }

    private static string GetIpFromString(string value)
    {
        var result = "";
        var ifThreeBreak = 0;
        foreach (var ipString in value)
        {
            if (ipString == '/') ifThreeBreak++;
            if (ifThreeBreak == 2 && ipString != '/') result += ipString;
            if (ifThreeBreak == 3) break;
        }
        return result;
    }

    public string GetFolderName(string path)
    {
        var name = "";
        var ifTwoBreak = 0;
        var indexOfNameStart = 0;
        var indexOfNameEnd = 0;
        for (int i = path.Length-1; i >= 0; i--)
        {
            if (path[i] == '\\')ifTwoBreak++;
            if (ifTwoBreak == 1)
            {
                indexOfNameEnd = i;
                ifTwoBreak++;
            } 
            if (ifTwoBreak == 3)
            { 
                indexOfNameStart = i + 1;
                break;
            }
        }

        for (var i = indexOfNameStart; i < indexOfNameEnd; i++)
        {
            name += path[i];
        }
        return name;
    }

    private void button_ClickNewConnection(object sender, RoutedEventArgs e)
    {
        MessageBox.Show(NewConnectionStringTextBox.Text);
    }

    private void button_ClickOldConnection(object sender, RoutedEventArgs e)
    {
        MessageBox.Show(OldConnectionStringTextBox.Text);
    }

    private void buttonStartService_Click(object sender, RoutedEventArgs e)
    {
        _serviceSwitcher.StartService(_client.Name);
    }
}

}

Clien.cs

public class Client : INotifyPropertyChanged
{
    private string _name;
    private string _ip;
    private string _connectionString;
    private string _webpageUri;

    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            OnPropertyChanged("Name");
        }
    }

    public string Ip
    {
        get { return _ip; }
        set
        {
            _ip = value;
            OnPropertyChanged("Ip");
        }
    }

    public string ConnectionString
    {
        get { return _connectionString; }
        set
        {
            _connectionString = value;
            OnPropertyChanged("ConnectionString");
        }
    }

    public string WebpageUri
    {
        get { return _webpageUri; }
        set
        {
            _webpageUri = value; 
            OnPropertyChanged("WebpageUri");
        }
    }

    public static Client GetClient()
    {
        var client = new Client()
        {
            _name = "Name",
            _connectionString = "ConnectionString",
            _ip = "Ip",
            _webpageUri = "WebpageUri"
        };
        return client;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

}

what do you think?

\$\endgroup\$
  • \$\begingroup\$ Reopened; you may want to edit again and fix the stray braces, I hope you get good reviews! :-) \$\endgroup\$ – Mathieu Guindon Dec 20 '15 at 17:59
  • 2
    \$\begingroup\$ A brief description of the purpose of the code would help reviewers greatly. Help us help you. \$\endgroup\$ – RubberDuck Dec 21 '15 at 12:37
5
\$\begingroup\$

I understand that you don't know MVVM yet. However one of the driving concepts behind it is to make a class that does the actual heavy lifting. Ideally this code seperation should be true if you decide to use MVVM later, or if you stick with code behind. Your question is probably that of how can this be applied? Simply stated you codebehind should create a new instance of a class and give it the appropriate values needed to perform a caclulation then return the transformed values. case in point:

private void button_ClickOld(object sender, RoutedEventArgs e)
{
    SelectVerifyAndLog(TextBoxOld, UpdateOldLabel);
}

private void button_ClickNew(object sender, RoutedEventArgs e)
{
    SelectVerifyAndLog(TextBoxNew, UpdateNewLabel);
}

private void SelectVerifyAndLog(TextBox textBox, Action updateLabel)
{
    //... code removed for clarity
}

you put all sorts of work in the SelectVerifyAndLog. One bit of work you do is check if a file is present and sets some text. One of the driving forces for using MVVM pattern is claimed testability. Well using codebehind has potential to still have well tested code. (just most frequently you'll see examples that show no testing, or testing ability). So what if you had a class that return a boolean value, and a success method? That is testable. Consider this class

public class FilePathChecker
{
    public string Message
    {
        get
        {
            return Success ? 
            "Correct folder select - success" :
            "Wrong folder selected - Config file doesn't exist";
        }
    }

    public bool Success {get; private set;}

    public void Check(string path, string fileName)
    {
        Success = FindFile(Path.Combine(path, fileName);
    }
    protected virtual bool FindFile(string filePath)
    {
        return File.Exists(filePath);
    }
}

it might be slightly over-engineered, but it gives me two things. First is that it gives me a class that I can easily change the outcome of a file is found (regardless if it really does or not) by simply extending FilePathChecker with a class that overrides FindFile and returns true or false as I see fit. Now testing that class isn't really worthwhile, but if I were to have a class that used a FilePathChecker that could give me some value. Now lets consider a class that would use it.

public class ClientPropertyFactory
{
    private readonly FilePathChecker _fileChecker;
    private readonly string _configFilePath;

    public ClientPropertyFactory(FilePathChecker fileChecker, string configFilePath)
    {
        _fileChecker = fileChecker;
        _configFilePath = configFilePath;
    }
    public Client Create(string folderDialog)
    {
        _fileChecker.Check(folderDialog, _configFilePath);
        if (_fileChecker.Success)
            //do the magic of creating a client
        else
            //report said error somehow.
    }
}

Here is where the value comes in because I can override FindFile in FilePathChecker easily, and I can pass in an instance of that easily info ClientPropertyFactory. So now all the math and resource finding things an go into the ClientPropertyFactory and be tested in a test framework (such as NUnit, or Microsoft's test framework). The underlying issue is why go through all that trouble to make 2 more classes just to return a client? Simply put for testing purposes. If all that code is testable then you know it works as you expect without having to run the actual application. It also gears your mind more towards the MVVM principle of seperating work that you do in the code behind with actual work that you want done. Also by seperating when you go back to this code and decide to use the MVVM approach you'll find it almost trivial. (Essentially it would be a matter of setting your button bindings to a method which is just the inside of your current button click events.) I personally think that this type of work is being overshadowed by different approaches such as MVVM. Granted WPF was built with MVVM in mind, but what if someone comes in and says oh MVVM is SOOOOoooo 2015, we use MVMVPM now, or your company decides that MVVM is to strange to work with and they only want MVC/MVP pattern used? No big deal because your code that does the actual heavy lifting is extracted and tucked away safely in a blanket of protection. (FYI.. I use MVVM, I like it and would recommend looking into it after you've learned how to seperate heavy lifting code from code behind)

\$\endgroup\$
  • \$\begingroup\$ My company don't use any patterns :/ I am not programmer, just start learn and I read about MVVM after wrote this app \$\endgroup\$ – Krzysztof Urbańczyk Dec 21 '15 at 18:47
6
\$\begingroup\$

A few quick tips:

  1. Margin should only be used to add an offset relative to element's position. You should never ever use Margin to define the position itself, if you want to have scalable layout (and you do). To create a grid layout, you should populate Grid.RowDefinitions and Grid.ColumnDefinitions properties of your Grid and then use Grid.Row and Grid.Column attached properties on your buttons, labels and texboxes. See this and this.
  2. You should learn default values of common dependency properties. For example deafult value of HorizontalAlignment property is Left, there is no point in setting it to Left explicitly in your xaml. You should only set dependency properties, when you want to override the default value.
  3. You should use StopServiceCheckBox.IsChecked.GetValueOrDefault() instead of StopServiceCheckBox.IsChecked != null && StopServiceCheckBox.IsChecked.Value.
  4. Don't name every single element in your xaml. Only name elements, if you need to access them via code-behind or data binding.
  5. You should probably implement a constructor with 4 parameters on your Client class.
\$\endgroup\$
  • \$\begingroup\$ 1. ResizeMode="NoResize" 2. Ok, but HorizontalAligment is set automatically 3. OK, i make this 4. I don't know yet how to use MVVM good, so i always have code-behind :/ 5. I try this. THX \$\endgroup\$ – Krzysztof Urbańczyk Dec 21 '15 at 17:40

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.