In my mvvm application I have a listview.
I like to show/hide some columns of the
listview, depending on the state of the checkbox "Show all columns" (Here: Col1 should be showed/hidden).
That's my very simplified code. What is wrong?!
Obviously this does't work!
<Window x:Class="WpfApplication1.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:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="185">
<Window.Resources>
<local:ConverterHideListViewColumn x:Key="ConverterHideListViewColumn" />
</Window.Resources>
<Grid>
<ListView Height="100" Width="100">
<ListView.View>
<GridView>
<GridViewColumn Header="Col0" Width="40"/>
<GridViewColumn Header="Col1" Width="{Binding ShowAllColumns, Converter={StaticResource ConverterHideListViewColumn}}"/>
</GridView>
</ListView.View>
</ListView>
<CheckBox
Content="Show all columns"
IsChecked="{Binding ShowAllColumns, Mode=TwoWay}"
Margin="40,140,0,0">
</CheckBox>
</Grid>
</Window>
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
VM _vm;
public MainWindow ()
{
InitializeComponent ();
_vm = new VM ();
this.DataContext = _vm;
}
}
/// <summary>
/// Dummy Viewmodel
/// </summary>
public class VM : INotifyPropertyChanged
{
private bool _bShowAllColumns;
public event PropertyChangedEventHandler PropertyChanged;
public VM ()
{
ShowAllColumns = true;
}
public bool ShowAllColumns
{
get { return _bShowAllColumns; }
set { _bShowAllColumns = value; }
}
private void OnPropertyChanged (string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler (this, new PropertyChangedEventArgs (propertyName));
}
}
/// <summary>
/// Converter for setting the ListView-Column width, depending on value VM.ShowAllColumns
/// </summary>
class ConverterHideListViewColumn : IValueConverter
{
public object Convert (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool bShowAllColumns = false;
double dWidth = 0;
if (value is bool)
{
bShowAllColumns = (bool) value;
dWidth = bShowAllColumns? 40 : 0;
}
return dWidth;
}
public object ConvertBack (object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotSupportedException ();
}
}
}
A GridViewColumn is not added to the visual tree and doesn't inherit any DataContext so you cannot bind its Width property to a source property of a view model without using a BindingProxy class as suggested here: https://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
This is all you need...
public bool ShowAllColumns
{
get { return _bShowAllColumns; }
set
{
if (_bShowAllColumns != value)
{
_bShowAllColumns = value;
OnPropertyChanged("ShowAllColumns");
}
}
}
Related
I have the following code:
XAML code:
<Window x:Class="combobinding.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:local="clr-namespace:combobinding"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Window.Resources>
<local:EnumConverter x:Key="isEnabledConverter" />
</Window.Resources>
<Grid>
<TextBox Text="Hello" IsEnabled="{Binding SectionTitle, Converter={StaticResource isEnabledConverter}}" />
</Grid>
</Window>
C# Code
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
public static readonly DependencyProperty SectionTitleProperty =
DependencyProperty.Register(nameof(SectionTitle),
typeof(SectionTitle),
typeof(MainWindow));
public SectionTitle SectionTitle
{
get { return (SectionTitle)GetValue(SectionTitleProperty); }
set { SetValue(SectionTitleProperty, value); }
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
SectionTitle = SectionTitle.TitleBlock;
}
}
public enum SectionTitle
{
Normal,
TitleBlock
}
public class EnumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var sectionType = (SectionTitle)value;
if (sectionType == SectionTitle.Normal)
return true;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
I would expect that the EnumConverter would be called as I am setting the DependencyProperty SectionTitle and any breakpoint inside the method will be hit.
However this doesn't seem to be the case; and the IsEnabled property is not being binded to SectionTitle as I wish.
What's wrong with this code?
The problem is the DataContext. The binding does not find its target.
You can set the context in the declaration of the window. Add this to the Window tag in your XAML:
DataContext="{Binding RelativeSource={RelativeSource Self}}"
Define Name property on your Window with Name="MyWindow", then use it in your binding like this:
<TextBox Text="Hello" IsEnabled="{Binding ElementName=MyWindow, Path=SectionTitle, Converter={StaticResource isEnabledConverter}}" />
You need to set the DataContext of your MainWindow. You can esaily do this inside the constructor:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
...
I have a C# Wpf project in which I have successfully loaded an Xps. file into a Document Viewer. I want to be able to have a variable in my C# code that notices a page change when you scroll the document.
So far I have figured out, that there is a function for the xaml code, that automatically changes the page number if you scroll to the next page:
<DocumentViewer x:Name="viewDocument" HorizontalAlignment="Left" VerticalAlignment="Top" Height="Auto" Grid.Row="0" Grid.Column="0" >
<FixedDocument></FixedDocument>
</DocumentViewer>
<TextBlock Text="{Binding ElementName=viewDocument,Path=MasterPageNumber}" Grid.Row="1"/>
My final goal is to sto pthe time the user spends on each page which is why I need to be able to connect the current page number with a variable in my code which I cannot do with the above example. I have tried to implement an INotifyPropertyChanged, but I am fairly new to C# and I cannot find the error. it sets the variable to the first page, but after that it doesn't update.
This is my View Model:
using System; using System.ComponentModel;
namespace Tfidf_PdfOnly {
public class MainViewModel : INotifyPropertyChanged
{
private int _myLabel;
public int MyLabel
{
get
{
return this._myLabel;
}
set
{
this._myLabel = value;
NotifyPropertyChanged("MyLabel");
}
}
public MainViewModel()
{
_myLabel = 55;
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}
and this is in my Document_Viewer.xaml.cs file
XpsDocument document1 = new XpsDocument(path, System.IO.FileAccess.Read);
//load the file into the viewer
viewDocument.Document = document1.GetFixedDocumentSequence();
MainViewModel vm = new MainViewModel();
this.DataContext = vm;
vm.MyLabel = viewDocument.MasterPageNumber;
To see if it works I bound it to a label on the UI:
<DocumentViewer x:Name="viewDocument" HorizontalAlignment="Left" VerticalAlignment="Top" Height="Auto" Grid.Row="0" Grid.Column="0" >
<FixedDocument></FixedDocument>
</DocumentViewer>
<TextBlock Text="{Binding MyLabel, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}" Grid.Row="1" HorizontalAlignment="Right"/>
I hope my question is clear, and any help is appreceated!
The DocumentViewer has a property named MasterPageNumber (which is supposed to be the page index of the document). The following sample uses Prism and the Blend SDK (behaviors). The converter is quick-and-dirty. For the timing, you could use StopWatch instances to track how long in between page changes.
MVVM Approach
ViewModel
public class ShellViewModel : BindableBase
{
private int _currentPage;
public string Title => "Sample";
public string DocumentPath => #"c:\temp\temp.xps";
public int CurrentPage
{
get => _currentPage;
set => SetProperty(ref _currentPage, value);
}
public ICommand PageChangedCommand => new DelegateCommand<int?>(i => CurrentPage = i.GetValueOrDefault());
}
View
<Window x:Class="Poc.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:viewModels="clr-namespace:Poc.ViewModels"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:behaviors="clr-namespace:Poc.Views.Interactivity.Behaviors"
xmlns:converters="clr-namespace:Poc.Views.Converters"
xmlns:controls1="clr-namespace:Poc.Views.Controls"
mc:Ignorable="d"
Title="{Binding Title}" Height="350" Width="525">
<Window.Resources>
<converters:PathToDocumentConverter x:Key="PathToDocumentConverter"></converters:PathToDocumentConverter>
</Window.Resources>
<Window.DataContext>
<viewModels:ShellViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
</Grid.RowDefinitions>
<DocumentViewer Document="{Binding DocumentPath,Converter={StaticResource PathToDocumentConverter}}">
<i:Interaction.Behaviors>
<behaviors:DocumentViewerBehavior PageViewChangedCommand="{Binding PageChangedCommand}"></behaviors:DocumentViewerBehavior>
</i:Interaction.Behaviors>
</DocumentViewer>
<TextBlock Grid.Row="1" Text="{Binding CurrentPage}"></TextBlock>
</Grid>
Behavior
public class DocumentViewerBehavior : Behavior<DocumentViewer>
{
public static readonly DependencyProperty PageViewChangedCommandProperty = DependencyProperty.Register(nameof(PageViewChangedCommand), typeof(ICommand), typeof(DocumentViewerBehavior));
public ICommand PageViewChangedCommand
{
get => (ICommand)GetValue(PageViewChangedCommandProperty);
set => SetValue(PageViewChangedCommandProperty, value);
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.PageViewsChanged += OnPageViewsChanged;
}
private void OnPageViewsChanged(object sender, EventArgs e) => PageViewChangedCommand?.Execute(AssociatedObject.MasterPageNumber);
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.PageViewsChanged -= OnPageViewsChanged;
}
}
Converter
public class PathToDocumentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var fileInfo = new FileInfo((string)value);
if (fileInfo.Exists)
{
if (String.Compare(fileInfo.Extension, ".XPS", StringComparison.OrdinalIgnoreCase) == 0)
{
return new XpsDocument(fileInfo.FullName, FileAccess.Read).GetFixedDocumentSequence();
}
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I've bound two radio buttons in my Child window to an Enum in my ViewModel which is constructed in the Main window. The binding works as expected but I have noticed a very odd behavior which I can't solve. I have provided all the code here so you can reconstruct the problem easily for yourself.
Here are the steps to see this odd behavior:
Click on the button in the MainWindow
The ChildWindow opens and the RadioButton is set to User
Choose Automatic and then Choose User again
Close the ChildWindow and reopen it again! Try to change the RadioButton to Automatic. It won't change!
<Window x:Class="RadioButtonBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Button Content="Display Child Window" Click="DisplayChildWindow"/>
</Window>
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var viewModel = new ViewModel();
DataContext = viewModel;
}
private void DisplayChildWindow(object sender, RoutedEventArgs e)
{
var win = new ChildWindow {DataContext = (ViewModel) DataContext};
win.ShowDialog();
}
}
<Window x:Class="RadioButtonBinding.ChildWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:radioButtonBinding="clr-namespace:RadioButtonBinding"
Title="ChildWindow" Height="300" Width="300">
<Window.Resources>
<radioButtonBinding:EnumBooleanConverter x:Key="EnumBooleanConverter"/>
</Window.Resources>
<StackPanel>
<RadioButton Content="Automatic"
GroupName="CalcMode"
IsChecked="{Binding Path=CalcMode,
Converter={StaticResource EnumBooleanConverter},
ConverterParameter={x:Static radioButtonBinding:CalcMode.Automatic}}"/>
<RadioButton Content="Custom"
GroupName="CalcMode"
IsChecked="{Binding Path=CalcMode,
Converter={StaticResource EnumBooleanConverter},
ConverterParameter={x:Static radioButtonBinding:CalcMode.User}}"/>
</StackPanel>
</Window>
public class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private CalcMode calcMode = CalcMode.User;
public CalcMode CalcMode
{
get { return calcMode; }
set
{
calcMode = value;
RaisePropertyChanged("CalcMode");
}
}
private void RaisePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler == null) return;
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class EnumBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var paramEnum = parameter as Enum;
var valueEnum = value as Enum;
return Equals(paramEnum, valueEnum);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
var parameterEnum = parameter as Enum;
if (parameterEnum == null)
return DependencyProperty.UnsetValue;
return parameterEnum;
}
}
public enum CalcMode : byte
{
Automatic,
User,
}
UPDATE:
I suspect it must be the Converter but I don't know why? It just falls into a loop.
EDIT
What about converting the enum to bool as follows?
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null || !(bool)value)
return DependencyProperty.UnsetValue;
var parameterEnum = parameter as Enum;
return parameterEnum;
}
This question is related to Validation Rule not updating correctly with 2 validation rules, but I'm not using ValidationRule, but the IDataErrorInfo interface.
Consider the following example, where the user has to select either option with a RadioButton, the validation then depends on the selection.
If Option 1 is selected, the entered string must begin with A, if option 2 is selected, it must begin with B.
If the user changes the option, the TextBox must be re-validated. As the answer provided in the linked question, using CurrentItem.ErrorContent in the error template does work, in that the error message is updated. (I only use CurrentItem.ErrorContent, (Validation.Errors).CurrentItem.ErrorContent displays nothing, but that's another issue I guess).
To reproduce, select option 1 and type in anything that doesn't start with A or B (e.g. VVVVV). Now select option 2.
The error message binding using the custom converter (displaying all error messages) still says that the string must begin with A, while the other binding in the error template correctly states that the string must begin with B.
Accoding to the answer provided there, this is because the ValidationRule prevents the actual property bound to to be updated, thus a NotifyPropertyChanged won't do anything. Here, however, the property is updated.
Is there a reason WPF doesn't call the converter again (bug or feature)?
Is there some way I can force the converter being called again, thus updating the first error message?
ValidationDemo.xaml:
<Window x:Class="ValidationIssue2.ValidationDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:validationIssue2="clr-namespace:ValidationIssue2"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<validationIssue2:ValidationViewModel />
</Window.DataContext>
<Window.Resources>
<validationIssue2:ValidationErrorsToStringConverter x:Key="ValErrToString" />
<ControlTemplate x:Key="ErrorTemplate">
<Border BorderBrush="Red" BorderThickness="1">
<StackPanel Orientation="Horizontal">
<AdornedElementPlaceholder />
<TextBlock Text="{Binding Converter={StaticResource ValErrToString}}" Background="White" />
<Label> / </Label>
<TextBlock Text="{Binding CurrentItem.ErrorContent}" Background="White" />
</StackPanel>
</Border>
</ControlTemplate>
</Window.Resources>
<StackPanel Orientation="Vertical">
<StackPanel>
<RadioButton IsChecked="{Binding Option1}">Option 1</RadioButton>
<RadioButton IsChecked="{Binding Option2}">Option 2</RadioButton>
</StackPanel>
<TextBox Validation.ErrorTemplate="{StaticResource ErrorTemplate}" Width="100" HorizontalAlignment="Left"
Text="{Binding ValidateThis, ValidatesOnDataErrors=True}"></TextBox>
</StackPanel>
</Window>
ValidationDemo.xaml.cs:
using System.Windows;
namespace ValidationIssue2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class ValidationDemo : Window
{
public ValidationDemo()
{
InitializeComponent();
}
}
}
ValidationErrorsToStringConverter.cs:
using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace ValidationIssue2
{
[ValueConversion(typeof(ReadOnlyObservableCollection<ValidationError>), typeof(string))]
internal class ValidationErrorsToStringConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var errorCollection = value as ReadOnlyObservableCollection<ValidationError>;
if (errorCollection == null)
{
return DependencyProperty.UnsetValue;
}
return String.Join(", ", errorCollection.Select(e => e.ErrorContent.ToString()));
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
ValidationViewModel.cs:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace ValidationIssue2
{
class ValidationViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private bool option1;
private bool option2;
private string validateThis;
public event PropertyChangedEventHandler PropertyChanged;
public bool Option1
{
get
{
return option1;
}
set
{
option1 = value;
OnPropertyChanged();
// since this does influece text validation
OnPropertyChanged("");
}
}
public bool Option2
{
get
{
return option2;
}
set
{
option2 = value;
OnPropertyChanged();
// since this does influece text validation
OnPropertyChanged("");
}
}
public string Error
{
get { return String.Empty; }
}
public string ValidateThis
{
get
{
return validateThis;
}
set
{
validateThis = value;
OnPropertyChanged();
}
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "ValidateThis":
if (Option1)
{
return ValidateThis.StartsWith("A") ? String.Empty : "Must start with A";
}
if (Option2)
{
return ValidateThis.StartsWith("B") ? String.Empty : "Must start with B";
}
return String.Empty;
default:
return String.Empty;
}
}
}
private void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I want to bind datagrid view column visibility with a property of class.
I am passing a collection as ItemSource to grid.
I am not able to do this. Any idea why?
This one is a bit tricky. The problem comes from the fact that DataGrid.Columns is just a property and not part of the visual tree. This means that normal binding tools like ElementName or RelativeSource will not work. If, however, you override the Metadata for the DataGrid.DataContext property, you can get it to work properly. Example code below:
<Window x:Class="DataGridColumnVisibilitySample.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tk="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
xmlns:l="clr-namespace:DataGridColumnVisibilitySample"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<l:VisibilityConverter x:Key="VisibilityC" />
</Window.Resources>
<DockPanel LastChildFill="True">
<CheckBox DockPanel.Dock="Top" Margin="8" Content="Show Column B" IsChecked="{Binding ShowColumnB}" />
<tk:DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" CanUserAddRows="False">
<tk:DataGrid.Columns>
<tk:DataGridTextColumn Header="Column A" Binding="{Binding ColumnA}" />
<tk:DataGridTextColumn Header="Column B" Binding="{Binding ColumnB}"
Visibility="{Binding (FrameworkElement.DataContext).ShowColumnB,
RelativeSource={x:Static RelativeSource.Self},
Converter={StaticResource VisibilityC}}" />
<tk:DataGridTextColumn Header="Column C" Binding="{Binding ColumnC}" />
</tk:DataGrid.Columns>
</tk:DataGrid>
</DockPanel>
</Window>
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Data;
using Microsoft.Windows.Controls;
namespace DataGridColumnVisibilitySample
{
public partial class Window1 : INotifyPropertyChanged
{
public Window1()
{
InitializeComponent();
new DataGridContextHelper(); // Initialize Helper
Items = Enumerable.Range(1, 3).Select(i => new MyItem {ColumnA = "A" + i, ColumnB = "B" + i, ColumnC = "C" + i}).ToList();
DataContext = this;
}
public List<MyItem> Items { get; private set; }
private bool mShowColumnB;
public bool ShowColumnB
{
get { return mShowColumnB; }
set
{
if (mShowColumnB == value) return;
mShowColumnB = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("ShowColumnB"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class DataGridContextHelper
{
static DataGridContextHelper()
{
FrameworkElement.DataContextProperty.OverrideMetadata(typeof(DataGrid),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnDataContextChanged));
}
public static void OnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as DataGrid;
if (grid == null) return;
foreach (var col in grid.Columns)
col.SetValue(FrameworkElement.DataContextProperty, e.NewValue);
}
}
[ValueConversion(typeof(bool), typeof(Visibility))]
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool && (bool)value)
return Visibility.Visible;
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class MyItem
{
public string ColumnA { get; set; }
public string ColumnB { get; set; }
public string ColumnC { get; set; }
}
}
I sourced this post by Jaime Rodriguez in creating my solution.
That works as long your datagrid is in a window, control, etc, if it's in a controltemplate this still won't work