7
\$\begingroup\$

Related to Resx Translation Helper, V.2.0 Remove Files Window

First, I implemented the changes suggested in my last question, then I changed my entire code base to support editing 1-N Resx files. So, this question covers the main window. In particular, I wasn't able to remove all the code from the code-behind, and I have not been able to find a way to do so.

IResxTranslationHelperWindow.cs:

public interface IResxTranslationHelperWindow
{
    object DataContext { set; }
    event EventHandler<DataGridCellEditEndingEventArgs> EndCellEdit;

    void Show();
}

ResxTranslationHelper.xaml:

<Grid>
    <Grid.Resources>
        <converters:StringListContainsNullOrEmptyValues x:Key="ArrayContainsEmptyVals" />
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition Height="40" />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Button Content="{x:Static properties:Resources.FilePickerButton_AddFile}" Command="{Binding PickFile}" />
        <Button Grid.Column="1" Content="{x:Static properties:Resources.FilePickerButton_RemoveFile}" Command="{Binding RemoveFile}" />
    </Grid>
    <DataGrid Name="GridDisplay" ItemsSource="{Binding ResxStrings, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True}"
              CanUserSortColumns="True" SelectionMode="Single" CellEditEnding="GridDisplay_CellEditEnding" Grid.Row="1"
              CanUserDeleteRows="True" CanUserAddRows="True" ColumnWidth="300" AutoGenerateColumns="False" AddingNewItem="GridDisplay_AddingNewItem"
              SourceUpdated="GridDisplay_SourceUpdated" TargetUpdated="GridDisplay_SourceUpdated">
        <DataGrid.InputBindings>
            <KeyBinding Key="Delete" Command="{Binding DeleteItem}" CommandParameter="{Binding ElementName=GridDisplay, Path=SelectedValue}" />
        </DataGrid.InputBindings>
        <DataGrid.RowStyle>
            <Style TargetType="DataGridRow">
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Values, Converter={StaticResource ArrayContainsEmptyVals}}" Value="true">
                        <Setter Property="Background" Value="Red" />
                    </DataTrigger>
                    <EventTrigger RoutedEvent="MouseEnter">
                        <BeginStoryboard Name="MouseEnterRow">
                            <Storyboard>
                                <ColorAnimation Storyboard.TargetProperty="Background.Color" To="LightGray" Duration="0" />
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                    <EventTrigger RoutedEvent="MouseLeave">
                        <RemoveStoryboard BeginStoryboardName="MouseEnterRow" />
                    </EventTrigger>
                </Style.Triggers>
            </Style>
        </DataGrid.RowStyle>
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Key}" Header="{x:Static properties:Resources.DataGridHeader_Key_Header}" CanUserSort="True" />
        </DataGrid.Columns>
    </DataGrid>
</Grid>

ResxTranslationHelper.xaml.cs:

private void GridDisplay_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
    if (e.EditAction == DataGridEditAction.Commit)
    {
        OnEndCellEdit(e);
    }
}

public event EventHandler<DataGridCellEditEndingEventArgs> EndCellEdit;
protected virtual void OnEndCellEdit(DataGridCellEditEndingEventArgs e)
{
    var handler = EndCellEdit;
    if (handler != null)
    {
        handler(this, e);
    }
}

private void RemoveColumns(DataGrid grid, int columnCount)
{
    while (grid.Columns.Count > columnCount)  // leave Keys column
    {
        grid.Columns.RemoveAt(columnCount);
    }
}

private void GridDisplay_SourceUpdated(object sender, DataTransferEventArgs e)
{
    var grid = (DataGrid) sender;
    var source = (ObservableCollection<ResxValues>) grid.ItemsSource;

    if (source.Count == 0 || source.First().Values.FirstOrDefault() == null)
    {
        RemoveColumns(grid, 1); // reset UI to startup
        return;
    }

    RemoveColumns(grid, source.First().Values.Count()); // remove removed columns

    // add added columns
    for (var i = grid.Columns.Count - 1; i < source.First().Values.Count(); i++)
    {
        var column = new DataGridTextColumn
        {
            Binding = new Binding(string.Format("Values[{0}]", i)),
            Header = Properties.Resources.DataGridHeader_Value_Header,
            CanUserSort = true
        };
        grid.Columns.Add(column);
    }
}

private void GridDisplay_AddingNewItem(object sender, AddingNewItemEventArgs e)
{
    var grid = (DataGrid) sender;

    var valCount = grid.Columns.Count - 1;
    var arr = new string[valCount];

    e.NewItem = new ResxValues(string.Empty, arr.Select(x => string.Empty).ToArray());
}

ResxTranslationHelperVM.cs:

private readonly IResxTranslationHelperWindow _window;
private readonly List<ResxFileComponents> _components;

// ReSharper disable once MemberCanBePrivate.Global
public ObservableCollection<ResxValues> ResxStrings { get; set; }

private ICommand _pickFile;
public ICommand PickFile
{
    get
    {
        return _pickFile ?? (_pickFile = new RelayCommand
        (
            param =>
            {
                var filePaths = OpenFilePicker();

                foreach (var filePath in filePaths.Where(filePath => !string.IsNullOrEmpty(filePath) && filePath.EndsWith(".resx") && _components.All(c => c.FilePath != filePath)))
                {
                    _components.Add(new ResxFileComponents(filePath));
                }

                ProcessData();
            }
        ));
    }
}

private ICommand _removeFile;
public ICommand RemoveFile
                {
    get
    {
        return _removeFile ?? (_removeFile = new RelayCommand
        (
            param =>
            {
                var filePaths = OpenFileCloser();

                foreach (var component in filePaths.Select(filePath => _components.FirstOrDefault(c => c.FilePath == filePath)).Where(component => component != null))
                {
                    _components.Remove(component);
                }

                ProcessData();
            }
        ));
    }
}

private ICommand _deleteItem;
public ICommand DeleteItem
{
    get
    {
        return _deleteItem ?? (_deleteItem = new RelayCommand
        (
            param => DeleteRow(ResxStrings.IndexOf((ResxValues) param))
        ));
    }
}

public ResxTranslationHelperVM(IResxTranslationHelperWindow window)
{
    _window = window;

    _components = new List<ResxFileComponents>();

    ResxStrings = new ObservableCollection<ResxValues>();

    _window.EndCellEdit += EndCellEdit;

    window.DataContext = this;
}

private IEnumerable<string> OpenFilePicker()
{
    var filePicker = new OpenFileDialog
    {
        Multiselect = true,
        SupportMultiDottedExtensions = true,
        CheckPathExists = true,
        Filter = @"Resx Files|*.resx",
        CheckFileExists = true,
        ValidateNames = true
    };

    filePicker.ShowDialog();
    return filePicker.FileNames;
}

private IEnumerable<string> OpenFileCloser()
{
    var filesSelected = new List<string>();

    var thread = new Thread(() =>
    {
        var displayOpenFilesVM = new DisplayOpenFilesVM(
            new DisplayOpenFiles(),
            _components.Select(c => c.FilePath));

        displayOpenFilesVM.Show();

        displayOpenFilesVM.FilesSelected += (s, e) =>
        {
            filesSelected = e.ToList();

            Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
        };

        Dispatcher.Run();
    });

    thread.SetApartmentState(ApartmentState.STA);
    thread.Start();
    thread.Join();

    return filesSelected;
}

private void EndCellEdit(object sender, DataGridCellEditEndingEventArgs e)
{
    var columnIndex = e.Column.DisplayIndex;

    var key = (string)e.Column.Header == Resources.DataGridHeader_Key_Header
              ? ((TextBox)e.EditingElement).Text
              : ((ResxValues)e.Row.Item).Key;

    if (columnIndex == 0)
    {
        if (string.IsNullOrEmpty(key) || ResxStrings.FirstOrDefault(s => s.Key == key) != null)
        {
            var message = string.IsNullOrEmpty(key)
                ? Resources.EndCellEdit_EmptyKeyError
                : Resources.EndCellEdit_KeyAlreadyExists;

            MessageBox.Show(message, Resources.EndCellEdit_AddValueError,
                MessageBoxButtons.OK, MessageBoxIcon.Warning);

            var newText = "String" + ResxStrings.Count(r => r.Key.StartsWith("String"));

            while (ResxStrings.FirstOrDefault(r => r.Key == newText) != null)
        {
                newText += new Random().Next(0, 9);
            }

            ((TextBox) e.EditingElement).Text = newText;
            key = newText;
        }

        foreach (var c in _components)
        {
            c.AddNode(key, c.GetNodeValue(((ResxValues)e.Row.Item).Key));
            c.RemoveNode(((ResxValues)e.Row.Item).Key);
            c.Save();
        }
        }
    else
    {
        _components.ElementAt(columnIndex - 1).RemoveNode(key);
        _components.ElementAt(columnIndex - 1).AddNode(key, ((TextBox)e.EditingElement).Text);
        _components.ElementAt(columnIndex - 1).Save();
    }
}

private void DeleteRow(int index)
{
    RemoveNode(ResxStrings[index].Key);
    SaveFiles();
    ResxStrings.RemoveAt(index);
}

private void SaveFiles()
{
    foreach (var c in _components)
    {
        c.Save();
    }
}

private void RemoveNode(string key)
{
    foreach (var c in _components)
    {
        c.RemoveNode(key);
}
}

public void Load()
{
    _window.Show();
}

private void ProcessData()
{
    ResxStrings.Clear();

    foreach (var c in _components.Where(c => _components.All(x => x.FilePath != c.FilePath)))
    {
        _components.Add(new ResxFileComponents(c.FilePath));
    }

    var keys = new List<string>();

    keys = _components.Aggregate(keys,
        (current, c) => current.Union(c.StringNodes.Select(n => n.FirstAttribute.Value)).ToList());

    var values = keys.Select(key => _components.Select(c => c.GetNodeValue(key)).ToArray()).ToList();

    for (var i = 0; i < keys.Count; i++)
    {
        ResxStrings.Add(new ResxValues(keys[i], values[i]));
}

    // ReSharper disable once ExplicitCallerInfoArgument
    OnPropertyChanged("ResxStrings");   // trigger for UI to load new columns
}

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    var handler = PropertyChanged;
    if (handler != null)
    {
        handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
object DataContext { set; }

Set-only properties smell. Give it a getter, it's begging for one. And the implementation has one already anyway.

In particular, I wasn't able to remove all the code from the code-behind, and I have not been able to find a way to do so.

Sure, is great and doing everything in is awesome. But this is also , and your code-behind is pretty much only concerned about the presentation of the grid - I see nothing wrong with doing that in the code-behind. That grid isn't exactly trivial, and is verbose by definition - well-written and documented plain code is a better way to do it IMO.

"Zero code-behind" is certainly achievable, only.. for simpler UI's, where data bindings do all the work. The idea behind that isn't that code-behind is evil - it's that the view should only be concerned with presentation; so long as you're not implementing view model logic in the code-behind, plain C# presentation code is perfectly fine.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.