MVVM Patttern: BindingExpression path error: 'Data' property not found on 'object' - wpf

I'm trying to use (for the first time) MVVM pattern to make a WPF chart work and I can't understand the problem! why I get nothing on my MainWindow while debugging !
in my output window i have this error message:
System.Windows.Data Error: 40 : BindingExpression path error: 'Data' property not found on >'object' ''String' (HashCode=-354185577)'. BindingExpression:Path=Data; DataItem='String'
(HashCode=-354185577); target element is 'ColumnSeries' (Name=''); target property is >'ItemsSource' (type 'IEnumerable')
here is my mainWindow.xaml in myProject.View
<Window x:Class="Chart.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns:diag="clr-namespace:System.Diagnostics;assembly=WindowsBase"
Title="MainWindow" Height="350" Width="525"
DataContext="Test">
<Grid>
<chartingToolkit:Chart
Height="262"
HorizontalAlignment="Left"
Margin="33,0,0,620"
Name="columnChart"
Title="ColumnSeriesDemo"
VerticalAlignment="Bottom"
Width="360">
<chartingToolkit:ColumnSeries
IndependentValueBinding="{Binding Path=DateTest, diag:PresentationTraceSources.TraceLevel=High}"
DependentValueBinding="{Binding Path=VolumeTest ,diag:PresentationTraceSources.TraceLevel=High}"
ItemsSource="{Binding Path=Data, Mode=TwoWay, diag:PresentationTraceSources.TraceLevel=High}" />
</chartingToolkit:Chart>
</Grid>
and here is the my mainWindow.cs
namespace Chart
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private BindingVM Test;
public MainWindow()
{
this.Test = new BindingVM();
this.DataContext = Test;
InitializeComponent();
}
}
}
here is my ModelView in MyProject.ModelView
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Chart.Model;
using System.Windows.Threading;
using System.ComponentModel;
namespace Chart.ViewModel
{
class BindingVM
{
public BindingVM()
{
// AddElement();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = TimeSpan.FromSeconds(1);
timer.Start();
}
DataItem item = new DataItem();
public DateTime DateTest
{
get { return item.date; }
set { item.date = value;
propChanged("date");
}
}
public Double VolumeTest
{
get { return item.volume; }
set
{
item.volume = value;
propChanged("volume");
}
}
public DispatcherTimer timer = new DispatcherTimer();
public event PropertyChangedEventHandler PropertyChanged;
public void propChanged(String propname)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propname));
}
}
public ObservableCollection<DataItem> DataTest = new ObservableCollection<DataItem>();
public ObservableCollection<DataItem> Data
{
get { return DataTest;}
set { DataTest= value;
propChanged("data");
}
}
public void timer_Tick(object sender, EventArgs e)
{
Random rnd = new Random();
double baseValue = 20 + rnd.NextDouble() * 10;
double value = baseValue + rnd.NextDouble() * 6 - 3;
DataTest.Add(new DataItem()
{
date = DateTime.Now,
open = value + rnd.NextDouble() * 4 - 2,
high = value + 2 + rnd.NextDouble() * 3,
low = value - 2 - rnd.NextDouble() * 3,
close = value + rnd.NextDouble() * 4 - 2,
volume = rnd.NextDouble() * 200,
});
baseValue = value < 6 ? value + rnd.NextDouble() * 3 : value;
// DataTest.RemoveAt(0);
}
}
}
and here is my model in myProject.Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Chart.Model
{
public class DataItem
{
public DateTime date { get; set; }
public double open { get; set; }
public double high { get; set; }
public double low { get; set; }
public double close { get; set; }
public double volume { get; set; }
}
}
Any help will be appreciated really :'(

Remove the line
DataContext="Test"
from the top of your XAML.
You are setting the DataContext in your code, but then setting it again to a string in your XAML.

I know this reply is extremely late, but i am sure it will help someone there.
i have spent hours because i faced this error that happen usually with beginners like me :
the error says:
BindingExpression path error: property not found on 'object'
and it is really clear message if I concentrated since the begining .
in my case I found that i defined the feild without the property
like the following:
public string Name = "textss";
and I the XAML processor want a property fully implemented like this:
public string _name = "textss";
public string Name{
get{
return _name;
}
set{
_name = value ;
}
}
or at least as the following
public string Name {get; set;}
so the answer to this question here is that the Date Property has some error !!
I think the error is in calling propChanged and passing the name with small letter "data" not "Data"
public ObservableCollection<DataItem> DataTest = new ObservableCollection<DataItem>();
public ObservableCollection<DataItem> Data
{
get { return DataTest;}
set { DataTest= value;
propChanged("data");
}
}

Related

Prism 5 and Unity - Manually resolve View with ViewModel wired up

Using: WPF, Prism 5, Unity
I am trying to write what I call a "WindowDialogService" which, when passed a type and called, will open a Window with that type as the content. My problem is I cannot get the ViewModel to instantiate and associate to the View.
I don't know if it is right but I am using AutoWireViewModel in my View and was assuming (obviously incorrectly) that the ViewModel would be "Located" when resolving using the Unity Container i.e. container.TryResolve <T> ()
So basically, I can resolve the View but the DataContext of the view is null.
I have included all relevant code below.
View
<dxc:DXWindow x:Class="ListClassList.Views.AddToClassView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
xmlns:dxe="clr-namespace:DevExpress.Xpf.Editors.Settings;assembly=DevExpress.Xpf.Core.v15.2"
xmlns:dxeditors="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:dxc="http://schemas.devexpress.com/winfx/2008/xaml/core"
xmlns:dxgt="http://schemas.devexpress.com/winfx/2008/xaml/grid/themekeys"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:extToolkit="http://schemas.xceed.com/wpf/xaml/toolkit"
WindowStartupLocation="CenterScreen"
dxc:ThemeManager.ThemeName="VS2010"
Title="{Binding Title}"
Width="800"
Height="500"
ResizeMode="CanResizeWithGrip"
>
<Grid>
</Grid>
</dxc:DXWindow>
ViewModel
using MyApp.Data;
using MyApp.Models.Model;
using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
namespace ListClassList.ViewModels
{
public class AddToClassViewViewModel : BindableBase
{
private readonly MyAppDbContext _context;
private ObservableCollection<MasterClass> _masterClasses;
public ObservableCollection<MasterClass> MasterClasses
{
get { return _masterClasses; }
set { SetProperty(ref _masterClasses, value); }
}
private ObservableCollection<Student> _students;
public ObservableCollection<Student> Students
{
get { return _students; }
set
{
SetProperty(ref _students, value);
}
}
public DelegateCommand FinishCommand { get; set; }
public AddToClassViewViewModel(IStudentTimetableService studentTimetableService)
{
}
}
}
IWindowDialogService Interface
using System;
using System.Windows;
namespace MyApp.Infrastructure.Services.Dialogs.WindowDialog
{
public interface IWindowDialogService
{
bool? ShowDialog<T>(Action onDialogClosed) where T : Window;
}
}
WindowDialogService Implementation
using System;
using System.Collections.Generic;
using System.Windows;
using Microsoft.Practices.ServiceLocation;
using Prism.Unity;
namespace MyApp.Infrastructure.Services.Dialogs.WindowDialog
{
public sealed class WindowDialogService : IWindowDialogService
{
private readonly List<Window> _openWindows = new List<Window>();
private static volatile WindowDialogService _instance;
private static readonly object SyncRoot = new Object();
private WindowDialogService() { }
public static WindowDialogService Instance
{
get
{
if (_instance == null)
{
lock (SyncRoot)
{
if (_instance == null)
_instance = new WindowDialogService();
}
}
return _instance;
}
}
public bool? ShowDialog<T>(Action onDialogClosed) where T : Window
{
try
{
using (var container = new Microsoft.Practices.Unity.UnityContainer())
{
var dialog = (Window)container.TryResolve <T> ();
dialog.Closed += (s, e) => onDialogClosed();
var result = dialog.ShowDialog();
return result;
}
}
catch (Exception)
{
throw;
}
}
}
}
Prism 6 does this automatically, for Prism 5 you need something along the lines of
ViewModelLocationProvider.SetDefaultViewModelFactory( type => Container.Resolve( type ) );
in your bootstrapper's ConfigureContainer

BitmapSource binding memory leak

The problem is the same with BitmapImage but for demonstration let's use BitmapSource. Basically if I'm showing a fairly large image in a view and I set the bound object to null when I'm done with it the image memory does not get reclaimed no matter how long you wait. The full code to reproduce the problem:
xaml:
<Window x:Class="TestBitmapSource.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:local="clr-namespace:TestBitmapSource"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<Button Command="{Binding Path=ReloadCommand}">Load</Button>
<Button Command="{Binding Path=ReleaseCommand}">Release</Button>
<Image Source="{Binding Path=MyImage}"></Image>
</StackPanel>
</Grid>
C#:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace TestBitmapSource
{
class MainViewModel : INotifyPropertyChanged
{
private BitmapSource myImage;
public BitmapSource MyImage
{
get { return myImage; }
set { myImage = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ICommand _reloadCommand;
public ICommand ReloadCommand
{
get
{
return _reloadCommand ?? (_reloadCommand = new CommandHandler(() => Reload(), true));
}
}
private ICommand _releaseCommand;
public ICommand ReleaseCommand
{
get
{
return _releaseCommand ?? (_releaseCommand = new CommandHandler(() => Release(), true));
}
}
Random random = new Random();
public void Reload()
{
int width = 6000;
int height = 6000;
int b = random.Next();
int g = random.Next();
int r = random.Next();
unsafe
{
byte* pixels = (byte*)Marshal.AllocHGlobal(width * height * 3);
for (int i = 0; i < width * height; i++)
{
pixels[i * 3] = (byte)b;
pixels[i * 3 + 1] = (byte)g;
pixels[i * 3 + 2] = (byte)r;
}
MyImage = BitmapSource.Create(width, height, 96, 96, PixelFormats.Bgr24, null, (IntPtr)pixels, width * height * 3, width * 3);
Marshal.FreeHGlobal((IntPtr)pixels);
MyImage.Freeze();
}
}
public void Release()
{
MyImage = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
public MainViewModel()
{
MyImage = null;
}
}
public class CommandHandler : ICommand
{
private Action _action; private bool _canExecute;
public CommandHandler(Action action, bool canExecute) { _action = action; _canExecute = canExecute; }
public bool CanExecute(object parameter) { return _canExecute; }
public event EventHandler CanExecuteChanged;
public void Execute(object parameter) { _action(); }
}
}
To reproduce the problem, click the Load button (which will create the image with BitmapSource.Create) and then the Release button which attempts to release the image and collect unused memory. However, the process monitor shows that the 250MB of memory is still in use, and I know that the MyImage object is still alive if I tag it with an Object ID in Visual Studio.
Two things I've found so far:
1) If I don't bind to the image in the view, everything is fine and the image object disappears immediately.
2) If you click the Release button a second time, the tagged object suddenly disappears and the memory is reclaimed.
So clearly, the act of binding creates some reference somewhere that is not released when the image object is set to null in the view model. I wanted to check if I'm missing something obvious before I get into a memory profiler.
Thanks

WPF DisplayMemberPath not Updating when SelectedItem is removed

I have simplified this problem down as much as I can. Basically I am overriding the "null" value of a combobox. So that if the item selected is deleted, it reverts back to "(null)". Unfortunately the behaviour of this is wrong, I hit delete, the ObservableCollection item is removed, thus the property binding is updated and it returns the "(null)" item as expected. But the combobox appearance shows blank. Yet the value its bound to is correct... this problem can be reproduced with the code below.
To reproduce this problem you select an item, and hit remove. Notice at this point the following line is called (when you remove the selected item). So its a good place to breakpoint.
if (m_Selected == null)
{
return Items[0]; //items 0 is ItemNull
}
Also notice that I have attmpted to fix it by Forcing a property update on the DisplayMemberPath. This did not work.
MainWindow.xaml
<Window x:Class="WPFCodeDump.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">
<StackPanel>
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding Selected, Mode=TwoWay}" DisplayMemberPath="Name"></ComboBox>
<Button Click="ButtonBase_OnClick">Remove Selected</Button>
</StackPanel>
</Window>
MainWindowViewModel.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace WPFCodeDump
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
//Item class
public class Item : ViewModelBase
{
public Item(string name)
{
m_Name = name;
}
public string Name
{
get { return m_Name; }
}
private string m_Name;
public void ForcePropertyUpdate()
{
OnPropertyChanged("Name");
}
}
//Item class
public class ItemNull : Item
{
public ItemNull()
: base("(null)")
{
}
}
class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
m_Items.Add(new ItemNull());
for (int i = 0; i < 10; i++)
{
m_Items.Add(new Item("TestItem" + i));
}
Selected = null;
}
//Remove selected command
public void RemoveSelected()
{
Items.Remove(Selected);
}
//The item list
private ObservableCollection<Item> m_Items = new ObservableCollection<Item>();
public ObservableCollection<Item> Items
{
get { return m_Items; }
}
//Selected item
private Item m_Selected;
public Item Selected
{
get
{
if (m_Selected == null)
{
return Items[0]; //items 0 is ItemNull
}
return m_Selected;
}
set
{
m_Selected = value;
OnPropertyChanged();
if(m_Selected!=null) m_Selected.ForcePropertyUpdate();
}
}
}
}
MainWindow.xaml.cs
using System.Windows;
namespace WPFCodeDump
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowViewModel();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
((MainWindowViewModel) DataContext).RemoveSelected();
}
}
}
Result:
A nice binding issue you found there. But as always, it's our fault, not theirs :)
The issue(s) is(are), using DisplayMemberPath with SelectedItem.
The DisplayMemberPath doesn't give a f*** about the changed SelectedItem.
What you have to do, to resolve this issue, are two things:
First, in the RemoveSelected method, set the Selected property to null (to force an update on the binding):
public void RemoveSelected()
{
Items.Remove(Selected);
Selected = null;
}
Then, in the XAML-definition, change the bound property:
<ComboBox ItemsSource="{Binding Items}"
SelectedValue="{Binding Selected, Mode=TwoWay}"
DisplayMemberPath="Name"/>
Binding the SelectedValue property will correctly update the displayed text in the ComboBox.

Hide a Column Using Data Annotations in WPF

I have a grid in WPF, auto-generating columns. How can I dynamically hide columns using data annotations?
I thought of having a property in my model to specify whether the column is visible, but I'm not sure how to do it.
My model, bound to the grid:
public class Template
{
public string County { get; set; }
public string Operator { get; set; }
public string Field { get; set; }
}
Here is a sample which uses attributes to hide columns. It uses an attached property to handle the AutoGeneratingColumn event.
HideColumnIfAutoGenerated.cs - Attribute
namespace AutoHideColumn
{
public class HideColumnIfAutoGenerated : System.Attribute
{
public HideColumnIfAutoGenerated()
{
}
}
}
DataGridExtension.cs - Attached Property
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace AutoHideColumn
{
public static class DataGridExtension
{
public static readonly DependencyProperty HideAnnotatedColumnsProperty = DependencyProperty.RegisterAttached(
"HideAnnotatedColumns",
typeof(bool),
typeof(DataGridExtension),
new UIPropertyMetadata(false, OnHideAnnotatedColumns));
public static bool GetHideAnnotatedColumns(DependencyObject d)
{
return (bool)d.GetValue(HideAnnotatedColumnsProperty);
}
public static void SetHideAnnotatedColumns(DependencyObject d, bool value)
{
d.SetValue(HideAnnotatedColumnsProperty, value);
}
private static void OnHideAnnotatedColumns(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool hideAnnotatedColumns = (bool)e.NewValue;
DataGrid dataGrid = d as DataGrid;
if (hideAnnotatedColumns)
{
dataGrid.AutoGeneratingColumn += dataGrid_AutoGeneratingColumn;
}
else
{
dataGrid.AutoGeneratingColumn -= dataGrid_AutoGeneratingColumn;
}
}
private static void dataGrid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
PropertyDescriptor propertyDescriptor = e.PropertyDescriptor as PropertyDescriptor;
if (propertyDescriptor != null)
{
foreach (var item in propertyDescriptor.Attributes)
{
if (item.GetType() == typeof(HideColumnIfAutoGenerated))
{
e.Cancel = true;
}
}
}
}
}
}
XAML
<Window x:Class="AutoHideColumn.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AutoHideColumn"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DataGrid Name="dg" local:DataGridExtension.HideAnnotatedColumns="True">
</DataGrid>
<DataGrid Grid.Row="1" Name="dg1">
</DataGrid>
</Grid>
</Window>
CodeBehind
using System.Collections.Generic;
using System.Windows;
namespace AutoHideColumn
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.dg.ItemsSource = new List<Customer>();
this.dg1.ItemsSource = new List<Customer>();
}
}
public class Customer
{
[HideColumnIfAutoGenerated()]
public int ID { get; set; }
public string Name { get; set; }
}
}
Try this
public partial class MainWindow : Window
{
private List<string> visibleColumns;
public MainWindow()
{
InitializeComponent();
InitializeList();
visibleColumns = GetVisibleColumns();
dg.AutoGeneratingColumn += dg_AutoGeneratingColumn;
dg.ItemsSource = Templates;
}
void dg_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if(!visibleColumns.Contains(e.Column.Header.ToString()))
e.Column.Visibility=Visibility.Collapsed;
}
List<string> GetVisibleColumns()
{
return typeof(Template).GetProperties()
.Where(p =>
p.GetCustomAttributes(typeof(Visible), true)
.Where(ca => ((Visible)ca).IsVisible).Any()
).Select(s => s.Name).ToList();
}
private void InitializeList()
{
Templates = new List<Template>();
Templates.Add(new Template { County = "abc", Operator = "123", Field = "xyz" });
Templates.Add(new Template { County = "abc", Operator = "123", Field = "xyz" });
Templates.Add(new Template { County = "abc", Operator = "123", Field = "xyz" });
Templates.Add(new Template { County = "abc", Operator = "123", Field = "xyz" });
}
public List<Template> Templates { get; set; }
}
>Template Class
public class Template
{
[Visible(false)]
public string County { get; set; }
[Visible(true)]
public string Operator { get; set; }
[Visible(true)]
public string Field { get; set; }
}
>Visible Attribute
public class Visible : Attribute
{
public Visible(bool isVisible)
{
IsVisible = isVisible;
}
public bool IsVisible { get; set; }
}
>xaml
<Grid>
<DataGrid AutoGenerateColumns="True" x:Name="dg"/>
</Grid>
this would be by far the most simplistic workaround which comes close to data annotation: omit the getter and setter.
public class Template
{
public string County { get; set; }
public string Operator { get; set; }
public string Field; //This is now a field and not a property-> invisible in datadrid
}
Attributes cannot be changed at run time hence you cannot dynamically hide columns using data annotations. But it is possible to dynamically hide columns. Here is an example which demonstrates it.
XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DataGrid ItemsSource="{Binding AutoGenerateColumnDetails}" CanUserAddRows="False" CanUserDeleteRows="False">
</DataGrid>
<Button Grid.Row="1" Content="Refresh" Click="Button_Click" HorizontalAlignment="Left" Margin="5"/>
<DataGrid Name="dg" Grid.Row="2" ItemsSource="{Binding TemplateList}" AutoGeneratingColumn="dg_AutoGeneratingColumn">
</DataGrid>
</Grid>
Code behind
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace AutoHideDGColumn
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private ViewModel _ViewModel = null;
public MainWindow()
{
InitializeComponent();
_ViewModel = new ViewModel();
this.DataContext = _ViewModel;
}
private void dg_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (_ViewModel.AutoGenerateColumnDetails.Where(d => d.HideColumn == true).Select(d => d.PropertyName).ToList().Contains(e.PropertyName))
e.Cancel = true;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Reset to trigger AutoGeneratingColumn event
this.dg.AutoGenerateColumns = false;
this.dg.AutoGenerateColumns = true;
}
}
public class ViewModel
{
private List<Template> _TemplateList;
public List<Template> TemplateList
{
get { return _TemplateList; }
set { _TemplateList = value; }
}
private List<AutoGenerateColumnDetail> _AutoGenerateColumnDetails;
public List<AutoGenerateColumnDetail> AutoGenerateColumnDetails
{
get { return _AutoGenerateColumnDetails; }
set { _AutoGenerateColumnDetails = value; }
}
public ViewModel()
{
AutoGenerateColumnDetails = typeof(Template).GetProperties().Select(p => new AutoGenerateColumnDetail() { PropertyName = p.Name }).ToList();
TemplateList = new List<Template>()
{
new Template() { County = "Count1", Field = "Field1", Operator = "Operator1"},
new Template() { County = "Count2", Field = "Field2", Operator = "Operator2"},
new Template() { County = "Count3", Field = "Field2", Operator = "Operator3"},
};
}
}
public class Template
{
public string County { get; set; }
public string Operator { get; set; }
public string Field { get; set; }
}
public class AutoGenerateColumnDetail
{
public string PropertyName { get; set; }
public bool HideColumn { get; set; }
}
}

Dynamic WPF datagrid with usercontrols

I've been at this for several times during the last couple of months, but I can't figure out how to do it.
I have a DataGrid that should show a clickable usercontrol in all columns except the first one, which should be a regular textcolumn without editing possibilities. The problem is that the number of columns has to be dynamic, there can be 2 to n ones.
Since I don't even know where to start I don't have any sample code.
If anyone could help getting me on track, I would be very grateful. The solution doesn't have to be proper MVVM or extremely fancy, it just has to work.
UPDATE 1 - A self-containing c# version.
Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.Specialized;
using System.Globalization;
namespace DynamicColumns
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded +=
(o, e) =>
{
this.PopulateItemsSource();
};
}
private void PopulateItemsSource()
{
int months = Math.Max(new Random().Next(12), 1);
this.d.ItemsSource =
new string[]
{
"John",
"Paul",
"Peter"
}.Select(t =>
MonthlyPerformance.CreateDummy(t, months)).ToList();
}
private void RePopulateButton_Click(object sender, RoutedEventArgs e)
{
this.PopulateItemsSource();
}
}
#region "Interfaces - must be in the shared between Objects & UI"
public interface IDynamicPropertiesObject
{
Dictionary<string, string> Properties { get; }
}
#endregion
#region "Objects"
public class MonthlyPerformance : IDynamicPropertiesObject
{
public string PerformerName
{
get;
set;
}
public Dictionary<string, string> Properties
{
get;
private set;
}
public static MonthlyPerformance CreateDummy(string performerName,
int months)
{
if (months < 1 || months > 12)
{
throw new ArgumentException(months.ToString());
}
Random random = new Random();
return new MonthlyPerformance()
{
PerformerName =
performerName,
Properties =
Enumerable.Range(1, months).ToDictionary(k => new DateTime(1, k, 1).ToString("MMM"), v => random.Next(100).ToString())
};
}
}
#endregion
#region "UI"
internal class DynamicPropertyValueConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
IDynamicPropertiesObject o = value as IDynamicPropertiesObject;
if (o != null)
{
return o.Properties[parameter.ToString()];
}
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class ExtendedDataGrid: DataGrid
{
public static readonly DependencyProperty IsDynamicColumnProperty =
DependencyProperty.RegisterAttached("IsDynamicColumn",
typeof(Boolean),
typeof(ExtendedDataGrid),
new PropertyMetadata(false));
private DynamicPropertyValueConverter converter = null;
public ExtendedDataGrid()
{
this.EnableColumnVirtualization = true;
this.EnableRowVirtualization = true;
this.AutoGenerateColumns = false;
}
private DynamicPropertyValueConverter Converter
{
get
{
if (this.converter == null)
{
converter = new DynamicPropertyValueConverter();
}
return this.converter;
}
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
this.ReGenerateColums();
}
private bool TryGetDynamicColumn(out DataGridColumn column)
{
column =
this.Columns.FirstOrDefault(t=>(bool)t.GetValue(ExtendedDataGrid.IsDynamicColumnProperty));
return column != null;
}
private void ClearDynamicColumns()
{
DataGridColumn column;
while (this.TryGetDynamicColumn(out column))
{
this.Columns.Remove(column);
}
}
private void ReGenerateColums()
{
this.ClearDynamicColumns();
if (this.Items.Count > 0)
{
IDynamicPropertiesObject o =
this.Items[0] as IDynamicPropertiesObject;
if (o != null)
{
foreach (KeyValuePair<string, string> property
in o.Properties)
{
DataGridTextColumn column =
new DataGridTextColumn()
{
Header = property.Key,
Binding = new Binding()
{
Converter = this.Converter,
ConverterParameter = property.Key
}
};
column.SetValue(ExtendedDataGrid.IsDynamicColumnProperty, true); // so we can remove it, when calling ClearDynamicColumns
this.Columns.Add(column);
}
}
}
}
}
#endregion
}
Markup:
<Window x:Class="DynamicColumns.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DynamicColumns"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Button x:Name="RePopulateButton" Grid.Row="0" Click="RePopulateButton_Click">Re-Populate</Button>
<local:ExtendedDataGrid x:Name="d" Grid.Row="1">
<local:ExtendedDataGrid.Columns>
<DataGridTextColumn Width="Auto" Binding="{Binding PerformerName}"/>
</local:ExtendedDataGrid.Columns>
</local:ExtendedDataGrid>
</Grid>
</Window>

Resources