Related to Resx Translation Helper, V.2.0 Remove Files WindowFirst, 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.Globalpublic 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));    }}
Resx Translation Helper, V.2.0
c#;wpf;localization
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, wpf is great and doing everything in xaml is awesome. But this is also c#, 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 xaml 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.