To aid in the localization of .resx files, I made a generic resx comparer that allows you to edit the values of the resx's.  Also, this is my first proper adventure in WPF (I've just been using Windows-runtime before).This is my view interface:public interface IResxTranslationHelperWindow{    object DataContext { get; set; }    event EventHandler<DataGridCellEditEndingEventArgs> EndCellEdit;    event EventHandler<ResxValues> DeleteRow;    void Show();}The view itself:<Grid Name=MainGrid>    <Grid.RowDefinitions>        <RowDefinition Height=40 />        <RowDefinition />    </Grid.RowDefinitions>    <StackPanel Orientation=Horizontal>        <StackPanel.Resources>            <converters:WindowWidthToHalf x:Key=WindowWidthToHalfConverter />        </StackPanel.Resources>        <Button Content={x:Static properties:Resources.FilePickerButton_BaseResx} Command={Binding PickFile} CommandParameter=DefaultResx                Width={Binding ElementName=MainGrid, Path=ActualWidth, Converter={StaticResource WindowWidthToHalfConverter}} />        <Button Content={x:Static properties:Resources.FilePickerButton_LocalizedResx} Command={Binding PickFile}                Width={Binding ElementName=MainGrid, Path=ActualWidth, Converter={StaticResource WindowWidthToHalfConverter}} />    </StackPanel>    <DataGrid ItemsSource={Binding ResxStrings} LoadingRow=GridDisplay_LoadingRow CanUserSortColumns=True               CanUserAddRows=True CanUserDeleteRows=True ColumnWidth=300 AutoGenerateColumns=False BeginningEdit=DataGrid_BeginningEdit              CellEditEnding=GridDisplay_CellEditEnding Grid.Row=1 PreviewKeyDown=DataGrid_PreviewKeyDown>        <DataGrid.Columns>            <DataGridTextColumn Binding={Binding Path=Key} Header={x:Static properties:Resources.DataGridHeader_Key_Header} CanUserSort=True />            <DataGridTextColumn Binding={Binding Path=Value} Header={x:Static properties:Resources.DataGridHeader_Value_Header} CanUserSort=True />            <DataGridTextColumn Binding={Binding Path=LocalizedValue} Header={x:Static properties:Resources.DataGridHeader_LocalizedValue_Header} CanUserSort=True />        </DataGrid.Columns>    </DataGrid></Grid>And the code-behind:public ResxTranslationHelperWindow(){    InitializeComponent();}private void GridDisplay_LoadingRow(object sender, DataGridRowEventArgs e){    var item = e.Row.Item as ResxValues;    if (item == null)    {        e.Row.Background = new SolidColorBrush(Colors.White);        return;    }    if (string.IsNullOrEmpty(item.LocalizedValue))    {        e.Row.Background = new SolidColorBrush(Colors.LightSeaGreen);    }    if (string.IsNullOrEmpty(item.Value))    {        e.Row.Background = new SolidColorBrush(Colors.Red);    }    if (!string.IsNullOrEmpty(item.Value) && !string.IsNullOrEmpty(item.LocalizedValue))    {        e.Row.Background = new SolidColorBrush(Colors.White);    }}private void DataGrid_BeginningEdit(object sender, DataGridBeginningEditEventArgs e){    // keys must not be edited unless they are empty, which signifies adding a new value    if (e.Column.DisplayIndex == 0)    {        var originalText = ((TextBlock) e.EditingEventArgs.OriginalSource).Text;        if (originalText != )        {            e.Cancel = true;        }    }}private void GridDisplay_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e){    if (e.EditAction == DataGridEditAction.Commit)    {        try        {            OnEndCellEdit(e);        }        catch (ArgumentException)        {            var grid = (DataGrid)sender;            var itemsLastRemoved = grid.ItemsSource.OfType<ResxValues>()                    .ToList();            itemsLastRemoved.RemoveAt(itemsLastRemoved.Count - 1);            grid.ItemsSource = new ObservableCollection<ResxValues>(itemsLastRemoved);            grid.SelectedItem = itemsLastRemoved.Last();        }    }}private void DataGrid_PreviewKeyDown(object sender, KeyEventArgs e){    if (Key.Delete == e.Key)    {        var grid = sender as DataGrid;        OnRowDeleted(grid.SelectedItem as ResxValues);    }}public event EventHandler<DataGridCellEditEndingEventArgs> EndCellEdit;protected virtual void OnEndCellEdit(DataGridCellEditEndingEventArgs e){    var handler = EndCellEdit;    if (handler != null)    {        handler(this, e);    }}public event EventHandler<ResxValues> DeleteRow;protected virtual void OnRowDeleted(ResxValues e){    var handler = DeleteRow;    if (handler != null)    {        handler(this, e);    }}My VM:private readonly IResxTranslationHelperWindow _window;private XElement _data;private XElement _localizedData;public ObservableCollection<ResxValues> ResxStrings { get; set; }public string DataPath { get; set; }public string LocalizedDataPath { get; set; }private ICommand _pickFile;public ICommand PickFile{    get    {        return _pickFile ?? (_pickFile = new RelayCommand        (            param =>            {                var filePath = OpenFilePicker();                if (filePath == ) { return; }                if (param as string == DefaultResx)                {                    DataPath = filePath;                }                else                {                    LocalizedDataPath = filePath;                }                LoadData();            }        ));    }}public ResxTranslationHelperVM(IResxTranslationHelperWindow window){    _window = window;    ResxStrings = new ObservableCollection<ResxValues>();    _window.EndCellEdit += EndCellEdit;    _window.DeleteRow += DeleteRow;    window.DataContext = this;}private string OpenFilePicker(){    var filePicker = new OpenFileDialog    {        Multiselect = false,        SupportMultiDottedExtensions = true,        CheckPathExists = true,        Filter = @Resx Files|*.resx    };    filePicker.ShowDialog();    return filePicker.FileName;}private void EndCellEdit(object sender, DataGridCellEditEndingEventArgs e){    var key = e.Column.DisplayIndex == 0              ? ((TextBox)e.EditingElement).Text              : ResxStrings[e.Row.GetIndex()].Key;    var value = e.Column.DisplayIndex == 1              ? ((TextBox)e.EditingElement).Text              : ResxStrings[e.Row.GetIndex()].Value;    var localizedValue = e.Column.DisplayIndex == 2                       ? ((TextBox)e.EditingElement).Text                       : ResxStrings[e.Row.GetIndex()].LocalizedValue;    if (e.Row.Item as ResxValues == ResxStrings.Last() &&        ResxStrings.Select(s => s.Key).Contains(key))    {        MessageBox.Show(Resources.EndCellEdit_KeyAlreadyExists, Resources.EndCellEdit_AddValueError, MessageBoxButtons.OK, MessageBoxIcon.Warning);        throw new ArgumentException(Invalid data);    }    RemoveNodes(ResxStrings[e.Row.GetIndex()].Key);    AddNode(_data, key, value);    AddNode(_localizedData, key, localizedValue);    SaveFiles();}private void DeleteRow(object sender, ResxValues e){    RemoveNodes(e.Key);    SaveFiles();    ResxStrings.Remove(e);}private void SaveFiles(){    _data.Save(DataPath);    _localizedData.Save(LocalizedDataPath);}private void RemoveNodes(string key){    var node = _localizedData.Nodes().OfType<XElement>().FirstOrDefault(n => n.FirstAttribute.Value == key);    if (node != null) { node.Remove(); }    node = _data.Nodes().OfType<XElement>().FirstOrDefault(n => n.FirstAttribute.Value == key);    if (node != null) { node.Remove(); }}private void AddNode(XElement element, string key, string value){    if (key == null) { return; }    XNamespace ns = http://www.w3.org/XML/1998/namespace;    var newValue = new XElement(data);    newValue.SetAttributeValue(name, key);    newValue.SetAttributeValue(ns + space, preserve);    newValue.SetElementValue(value, value);    element.Add(newValue);}public void Load(){    _window.Show();}private void LoadData(){    if (string.IsNullOrEmpty(DataPath) || string.IsNullOrEmpty(LocalizedDataPath))    {        return;    }    ResxStrings.Clear();    _data = XElement.Load(DataPath);    var dataStrings = _data.Nodes().OfType<XElement>().Where(n => n.LastAttribute.Name.LocalName == space                                                               && n.LastAttribute.Value == preserve                                                               && n.FirstAttribute.Name.LocalName == name);    _localizedData = XElement.Load(LocalizedDataPath);    var localizedDataStrings = _localizedData.Nodes().OfType<XElement>().Where(n => n.LastAttribute.Name.LocalName == space                                                                   && n.LastAttribute.Value == preserve                                                                   && n.FirstAttribute.Name.LocalName == name).ToList();    // add all strings in data    foreach (var node in dataStrings)    {        var localizedValue = localizedDataStrings.FirstOrDefault(n => n.FirstAttribute.Value == node.FirstAttribute.Value);        ResxStrings.Add(new ResxValues(node.FirstAttribute.Value, node.Value.Trim(), localizedValue == null ? null : localizedValue.Value.Trim()));    }    // add all strings in localized data not already added    foreach (var node in localizedDataStrings.Where(n => !ResxStrings.Select(s => s.Key).Contains(n.FirstAttribute.Value)))    {        ResxStrings.Add(new ResxValues(node.FirstAttribute.Value, null, node.Value.Trim()));    }}public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null){    var handler = PropertyChanged;    if (handler != null)    {        handler(this, new PropertyChangedEventArgs(propertyName));    }}The App startup file:protected override void OnStartup(StartupEventArgs e){    var translationHelperVM = new ResxTranslationHelperVM(new ResxTranslationHelperWindow());    translationHelperVM.Load();}The ResxValues class:public string Key { get; set; }public string Value { get; set; }public string LocalizedValue { get; set; }public ResxValues(string key, string value, string localizedValue){    Key = key;    Value = value;    LocalizedValue = localizedValue;}public ResxValues() : this(, , ) { }And the RelayCommand class:public event EventHandler CanExecuteChanged;private readonly Action<object> _execute;public RelayCommand(Action<object> execute){    _execute = execute;}public bool CanExecute(object parameter){    return true;}public void Execute(object parameter){    _execute(parameter);}You can view (and use) the whole project on GitHub, and of course all comments are welcome.
Resx Editor in WPF
c#;wpf;xaml;localization
I would like to add a few thoughts on WPF part of your code:If you need your buttons to occupy half the screen, you should simply replace StackPanel with Grid:<Grid>   <Grid.ColumnDefinitions>       <ColumnDefinition/>       <ColumnDefinition/>   <Grid.ColumnDefinitions></Grid>This is not the case where you should use a converter.GridDisplay_LoadingRow - now this is a different story. Here converter will fit nicely. You can data bind your data grid item to Background property, and use a converter to set the appropriate Brush depending on item properties. You can use regular converter, or you can use a multibinding. The latter wil update background dynamically, if you implement INotifyPropertyChanged interface on your ResxValues class.DataGrid_BeginningEdit - and what if user added a new item but noticed a typo afterwards? Why can't he edit it? He can remove the entire row and re-add it though. Thats a really weird design from UX standpoint. :) If you want to keep this logic, I would suggest using xaml and data binding instead. You can use DataGridTemplateColumn with custom CellEditingTemplate which will be a TextBox for editable cells or TextBlock for read only cells. You will select one or the other by binding to your data grid items. This might be a bit complicated if you've just started learning wpf though.GridDisplay_CellEditEnding - so, if user makes a mistake you remove the entire row? :) That's not very user-friendly. I am not sure I understand, why you are recreating ItemsSource either, seems fishy to me (will it update the collection on your viewmodel? or will it break the binding?). WPF have an inbuild system for validating data errors. Normally you would want to let user know, that he made an error (show tooltip, highlight with red border, etc.) and give him a chance to fix it.DataGrid_PreviewKeyDown - you should use InputBindings instead.<DataGrid.InputBindings>    <KeyBinding Key=Delete                 Command={Binding DeleteCommandOnYourViewModel}                 CommandParameter={Binding ElementName=YourDataGridName, Path=SelectedItem}/></DataGrid.InputBindings>If you choose to follow the above suggestions, your code behind file will look like this after the refactoring:public ResxTranslationHelperWindow(){    InitializeComponent();}