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
Related
I'm simply creating a Observable collection in Background and binding to it.
All objects inherit from INotifyPropertyChanged.
but nevertheless the Memory consumption is continuously raising.
The following Objects instances are continuously raising
WeakReference
FrugalObjectList<WeakEventManager+Listener>
ConditionalWeakTable<TKey, TValue>+Entry<Object, Object>[]
WeakEventTable+EventKey
ConditionalWeakTable<Object, Object>
SingleItemList<WeakEventManager+Listener>
Object
Int32[]
WeakEventManager+ListenerList<NotifyCollectionChangedEventArgs>
WeakEventManager+ListenerList<PropertyChangedEventArgs>
HybridDictionary
ListDictionary
ListDictionary+DictionaryNode
WeakEventManager+ListenerList<EventArgs>
WeakEventManager+ListenerList<CurrentChangingEventArgs>
CollectionRecord
I'm using .net 4.5.2.
See also the following Screenshots:
MemoryConsumptionOverview
ClassesIncreasing
Attached the sample code
XAML Markup:
<Window x:Class="BindingDataGridTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="120" Width="200"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<DataGrid ItemsSource="{Binding BindingVals, Mode=OneWay}" />
</Grid>
</Window>
Code behind:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Threading;
namespace BindingDataGridTest
{
public partial class MainWindow : INotifyPropertyChanged
{
public ObservableCollection<Values> BindingVals { get; set; }
public MainWindow()
{
BindingVals = new ObservableCollection<Values>();
InitializeComponent();
DispatcherTimer myTimer = new DispatcherTimer { Interval = new TimeSpan(20) };
myTimer.Tick += CreateVals;
myTimer.Start();
}
private void CreateVals(object sender, EventArgs e)
{
Values myMainval = new Values
{
MyVal = "1V" + new Random().Next()
};
BindingVals.Clear();
BindingVals.Add(myMainval);
GC.Collect();
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class Values : INotifyPropertyChanged
{
public string MyVal { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
}
It's caused by DispatcherTimer.
A DispatcherTimer will keep an object alive whenever the object's methods are bound to the timer.
https://msdn.microsoft.com/ru-ru/library/system.windows.threading.dispatchertimer(v=vs.110).aspx
Use System.Timers.Timer instead and Dispatcher separately.
public MainWindow()
{
BindingVals = new ObservableCollection<Values>();
InitializeComponent();
System.Timers.Timer myTimer = new Timer {Interval = 20};
myTimer.Elapsed += CreateVals;
myTimer.Start();
}
private void CreateVals(object sender, EventArgs e)
{
Dispatcher.Invoke(() =>
{
Values myMainval = new Values
{
MyVal = "1V" + new Random().Next()
};
BindingVals.Clear();
BindingVals.Add(myMainval);
GC.Collect();
});
}
I’m having some problems with OxyPlot that I have not been able to resolve through their documentation or other searches. I’m working on a wpf application that will allow the user to open a .csv with a button-click event, then perform some math and report back some useful information. I’d like to plot some of the generated data hence OxyPlot. For some reason I cannot get the plot to populate, when the code that generates it, is within the button click event. To illustrate here is a smaller example:
This code works (xaml):
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:oxy="http://oxyplot.org/wpf"
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="350" Width="525">
<Grid>
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" Margin="20,20,0,0" VerticalAlignment="Top" Width="75" Click="button_Click"/>
<Grid HorizontalAlignment="Left" Height="255" Margin="20,47,0,0" VerticalAlignment="Top" Width="477">
<oxy:PlotView Model="{Binding ScatterModel}"/>
</Grid>
</Grid>
with this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
var tmp = new PlotModel { Title = "Scatter plot", Subtitle = "y = x" };
var s2 = new LineSeries
{
StrokeThickness = 1,
MarkerSize = 1,
MarkerStroke = OxyColors.ForestGreen,
MarkerType = MarkerType.Plus
};
for (int i = 0; i < 100; i++)
{
s2.Points.Add(new DataPoint(i, i));
}
tmp.Series.Add(s2);
this.ScatterModel = tmp;
}
private void button_Click(object sender, RoutedEventArgs e)
{
}
public PlotModel ScatterModel { get; set; }
And produces this:
Plot Working
But, without changing the xaml, if I copy/paste the code beneath the button click event:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
DataContext = this;
var tmp = new PlotModel { Title = "Scatter plot", Subtitle = "y = x" };
var s2 = new LineSeries
{
StrokeThickness = 1,
MarkerSize = 1,
MarkerStroke = OxyColors.ForestGreen,
MarkerType = MarkerType.Plus
};
for (int i = 0; i < 100; i++)
{
s2.Points.Add(new DataPoint(i, i));
}
tmp.Series.Add(s2);
this.ScatterModel = tmp;
}
public PlotModel ScatterModel { get; set; }
The plot never generates: Not working:
I’ve tried moving DataContext = this; back up to public MainWindow(), and vice-versa with InitializeComponent(); no change. I’ve also tried defining
<Window.DataContext>
<local:MainWindow/>
</Window.DataContext>
in the xaml but that throws an exception/infinite loop error during build.
Something simple I fear I'm not getting about OxyPlot implementation?
Thanks!
CSMDakota
INotifyPropertyChanged keeps your view in sync with the program's state. One way to do this is by implementing a ViewModel (the MVVM pattern).
So let's create one. ViewModelBase introduces OnPropertyChanged(), the method that updates ScatterModel.
ViewModels.cs
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using OxyPlot;
namespace WpfApplication1
{
public class ViewModel : ViewModelBase
{
private PlotModel _scatterModel;
public PlotModel ScatterModel
{
get { return _scatterModel; }
set
{
if (value != _scatterModel)
{
_scatterModel = value;
OnPropertyChanged();
}
}
}
}
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] String propName = null)
{
// C#6.O
// PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
if (PropertyChanged != null)
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propName));
}
}
}
In MainWindow.xaml you can now add
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
var tmp = new PlotModel { Title = "Scatter plot", Subtitle = "y = x" };
var s2 = new LineSeries
{
StrokeThickness = 1,
MarkerSize = 1,
MarkerStroke = OxyColors.ForestGreen,
MarkerType = MarkerType.Plus
};
for (int i = 0; i < 100; i++)
{
s2.Points.Add(new DataPoint(i, i));
}
tmp.Series.Add(s2);
ViewModel.ScatterModel = tmp;
}
// C#6.O
// public ViewModel ViewModel => (ViewModel)DataContext;
public ViewModel ViewModel
{
get { return (ViewModel)DataContext; }
}
}
Note we're no longer setting DataContext = this, which is considered bad practice. In this case the ViewModel is small, but as a program grows this way of structuring pays off.
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
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");
}
}
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>