C# - ReportViewer - make class not static - c#

I have a class DependencyReportViewer that is correspond for creation a ReportViewer control in my program. This class works perfectly. In XAML we define only this line for each report that we wants:
<ctr:DependencyReportViewer EmbeddedReportName="Program.View.Reports.ReportName.rdlc" ReportData="{Binding ReportData}"/>
First part of the class:
<WindowsFormsHost x:Class="Program.View.Controls.DependencyReportViewer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
IsVisibleChanged="WindowsFormsHost_IsVisibleChanged">
</WindowsFormsHost>
And the worst part of the class:
public partial class DependencyReportViewer : WindowsFormsHost
{
private static ReportViewer ReportViewer = new ReportViewer();
private static int ReportIsRunning = 0;
static DependencyReportViewer()
{
DependencyReportViewer.ReportViewer.SetDisplayMode(DisplayMode.PrintLayout);
try
{
RenderingExtensions = ReportViewer.LocalReport.ListRenderingExtensions();
}
catch (Exception)
{
RenderingExtensions = new RenderingExtension[0];
}
ReportViewer.ReportError += ReportViewer_ReportError;
}
public static RenderingExtension[] RenderingExtensions { get; private set; }
public static readonly RoutedEvent ReportDoneEvent = EventManager.RegisterRoutedEvent("ReportDone", RoutingStrategy.Bubble,
typeof(RoutedEventHandler), typeof(DependencyReportViewer));
public event RoutedEventHandler ReportDone
{
add { AddHandler(ReportDoneEvent, value); }
remove { RemoveHandler(ReportDoneEvent, value); }
}
public string EmbeddedReportName
{
get { return (string)GetValue(EmbeddedReportNameProperty); }
set { SetValue(EmbeddedReportNameProperty, value); }
}
public static readonly DependencyProperty EmbeddedReportNameProperty = DependencyProperty.Register("EmbeddedReportName",
typeof(string), typeof(DependencyReportViewer));
public Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>> ReportData
{
get { return (Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>)GetValue(ReportDataProperty); }
set { SetValue(ReportDataProperty, value); }
}
public static readonly DependencyProperty ReportDataProperty = DependencyProperty.Register("ReportData",
typeof(Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>), typeof(DependencyReportViewer),
new FrameworkPropertyMetadata(null, ReportData_Changed));
private static void ReportData_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var sts = Interlocked.CompareExchange(ref ReportIsRunning, 1, 0);
if (sts != 0)
return;
var rv = (DependencyReportViewer)d;
var data = (Tuple<IEnumerable<Tuple<string, IEnumerable>>, IEnumerable<Tuple<string, string>>>)e.NewValue;
RenderingCompleteEventHandler evt = null;
evt = (sender, rea) =>
{
ReportViewer.RenderingComplete -= evt;
sts = Interlocked.Exchange(ref ReportIsRunning, 0);
if (sts == 1)
rv.RaiseEvent(new RoutedEventArgs(ReportDoneEvent));
};
ReportViewer.RenderingComplete += evt;
DependencyReportViewer.ReportViewer.Reset();
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = null;
if (data != null)
{
DependencyReportViewer.ReportViewer.LocalReport.SetBasePermissionsForSandboxAppDomain(new System.Security.PermissionSet(System.Security.Permissions.PermissionState.Unrestricted));
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = rv.EmbeddedReportName;
var l = new List<ReportParameter>();
foreach (var item in data.Item2)
{
l.Add(new ReportParameter(item.Item1, item.Item2));
}
DependencyReportViewer.ReportViewer.LocalReport.SetParameters(l);
foreach (var item in data.Item1)
{
var rds = new ReportDataSource(item.Item1);
rds.Value = item.Item2;
DependencyReportViewer.ReportViewer.LocalReport.DataSources.Add(rds);
}
}
DependencyReportViewer.ReportViewer.RefreshReport();
}
private static void ReportViewer_ReportError(object sender, ReportErrorEventArgs e)
{
Interlocked.Exchange(ref ReportIsRunning, 0);
}
public DependencyReportViewer()
{
InitializeComponent();
}
private void WindowsFormsHost_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
this.Child = DependencyReportViewer.ReportViewer;
}
else
{
DependencyReportViewer.ReportViewer.Reset();
DependencyReportViewer.ReportViewer.LocalReport.ReportEmbeddedResource = null;
DependencyReportViewer.ReportViewer.LocalReport.DataSources.Clear();
DependencyReportViewer.ReportViewer.RefreshReport();
this.Child = null;
}
}
}
The problem is that this code works normally only in situations, when we have only one UserControl with this ReportViewer opened. If we open two or more controls, then the last open control will take over ReportViewer. In other words it is possible to open only one ReportViewer at the same time.
Now I want to have a possibility to open multiple controls with multiple report viewers. As I get the problem is that this class is static. And when we define a new control, then in "this.Child = DependencyReportViewer.ReportViewer;" line we just reset this viewer to the newest control.
But the man who wrote this had a skill much greater then I have now. I failed multiple times to set this class nonstatic with XamlParseException. How can I convert this class to nonstatic?

Related

Specified element is already the logical child of another element. Disconnect it first. in User Control

I have this UserControl:
[ContentProperty("Items")]
[DefaultProperty("Items")]
public partial class PanelControl : UserControl
{
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register("Orientation", typeof(Orientation), typeof(PanelControl), new FrameworkPropertyMetadata(Orientation.Horizontal, new PropertyChangedCallback(OnOrientationChanged)));
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("Items", typeof(ObservableCollection<UIElement>), typeof(PanelControl), new FrameworkPropertyMetadata(new ObservableCollection<UIElement>(), new PropertyChangedCallback(OnItemsChanged)));
public static readonly DependencyProperty SizeProperty = DependencyProperty.RegisterAttached("Size", typeof(double), typeof(PanelControl), new FrameworkPropertyMetadata(1.0, new PropertyChangedCallback(OnSizeChanged)), new ValidateValueCallback(IsSizeValid));
public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
public ObservableCollection<UIElement> Items
{
get
{
return (ObservableCollection<UIElement>)GetValue(ItemsProperty);
}
set
{
SetValue(ItemsProperty, value);
}
}
public static void SetSize(UIElement element, double size)
{
element.SetValue(SizeProperty, size);
}
public static double GetSize(UIElement element)
{
return (double)element.GetValue(SizeProperty);
}
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("orientation");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
}
private static void OnItemsChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
/*MessageBox.Show("items");*/
((PanelControl)dependencyObject).ClearAndBuildGrid();
if(args.OldValue != null)
((ObservableCollection<UIElement>)args.OldValue).CollectionChanged -= ((PanelControl)dependencyObject).OnItemsCollectionChanged;
if (args.NewValue != null)
((ObservableCollection<UIElement>)args.NewValue).CollectionChanged += ((PanelControl)dependencyObject).OnItemsCollectionChanged;
}
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
/*MessageBox.Show("collection");*/
ClearAndBuildGrid();
}
private static void OnSizeChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
((PanelControl)dependencyObject).ClearAndBuildGrid();
/*MessageBox.Show("size");*/
}
private static bool IsSizeValid(object value)
{
return (double)value < 0 ? false : true;
}
private void ClearAndBuildGrid()
{
MainGrid.Children.Clear();
MainGrid.RowDefinitions.Clear();
MainGrid.ColumnDefinitions.Clear();
/*MessageBox.Show(MainGrid.Children.Count.ToString());*/
for (int i = 0; i < Items.Count; i++)
{
if (Orientation == Orientation.Horizontal)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = SizeToGridLength(GetSize(Items[i])) });
Grid.SetColumn(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Columns, HorizontalAlignment = HorizontalAlignment.Stretch };
Grid.SetColumn(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
else
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = SizeToGridLength(GetSize(Items[i])) });
Grid.SetRow(Items[i], i * 2);
if (i != Items.Count - 1)
{
MainGrid.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(5) });
GridSplitter splitter = new GridSplitter() { ResizeDirection = GridResizeDirection.Rows, VerticalAlignment = VerticalAlignment.Stretch };
Grid.SetRow(splitter, i * 2 + 1);
MainGrid.Children.Add(splitter);
}
}
MainGrid.Children.Add(Items[i]);
}
}
private GridLength SizeToGridLength(double size)
{
return new GridLength(size, GridUnitType.Star);
}
public PanelControl()
{
InitializeComponent();
Items.CollectionChanged += OnItemsCollectionChanged;
}
}
And I use it here:
<p:PanelControl>
<Button />
<Button />
<Button />
</p:PanelControl>
When i start application it works good, but in designer I have underlined first button in xaml and error "Specified element is already the logical child of another element. Disconnect it first." Thanks for help, sorry for my bad English.
Not sure what is going on with the designer but this will 'fix' it.
Change the line:
MainGrid.Children.Add(Items[i]);
To:
var parent = VisualTreeHelper.GetParent(Items[i]) as Grid;
if (parent != null)
parent.Children.Remove(Items[i]);
MainGrid.Children.Add(Items[i]);
Using the code from J.H., I would move it up to the top of your function, so that the state of your grid when you clear it, is "cleared", and all the children have been disconnected from the Visual Tree.
private void ClearAndBuildGrid()
{
foreach (var item in Items)
{
var parent = System.Windows.Media.VisualTreeHelper.GetParent(item) as Grid;
if (parent != null)
parent.Children.Remove(item);
}
MainGrid.Children.Clear();
It would be a style/intention/preference thing, and to be clear, the answer J.H. gives is totally valid.
Consider using foreach instead of for and not having to deal with array subscripts.

WPF MVVM Dynamic Columns Multi-Threaded

I am currently working on a C# System.Windows.Controls.DataGrid that needs to generate the columns dynamically depending on the data. It can add and/or remove columns during runtime.
I am using a Thread in the ViewModel class to update the ObservableCollection that feeds the DataGrid.
I have read that post which explains the best solution I have found for my problem. Although, the Columns.CollectionChanged Delegate from the DataGridExtension class throws a InvalideOperationException : The calling thread cannot access this object because a different thread owns it.
Heres some code to picture it all :
The View XAML
<DataGrid ItemsSource="{Binding CollectionView, Source={StaticResource ViewModel}}" local:DataGridExtension.Columns="{Binding DataGridColumns, Source={StaticResource ViewModel}}" AutoGenerateColumns="False" Name="dataGrid">
ViewModel Class
public ObservableCollection<DataGridColumn> DataGridColumns
{
get { return columns; }
set { columns = value; }
}
private void getViewData()
{
while (true)
{
Thread.Sleep(1000);
foreach (DataObject data in dataObjects)
{
int index = -1;
foreach (DataGridColumn c in columns)
{
if (c.Header.Equals(column.Header))
index = columns.IndexOf(c);
}
DataGridColumn column = new DataGridTextColumn();
... Creating the column based on data from DataObject ...
DataGridExtension._currentDispatcher = Dispatcher.CurrentDispatcher;
if (index == -1)
{
this.columns.Add(column);
}
else
{
this.columns.RemoveAt(index);
this.columns.Add(column);
}
}
}
}
DataGridExtension class
public static class DataGridExtension
{
public static Dispatcher _currentDispatcher;
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.RegisterAttached("Columns",
typeof(ObservableCollection<DataGridColumn>),
typeof(DataGridExtension),
new UIPropertyMetadata(new ObservableCollection<DataGridColumn>(), OnDataGridColumnsPropertyChanged));
private static void OnDataGridColumnsPropertyChanged(DependencyObject iObj, DependencyPropertyChangedEventArgs iArgs)
{
if (iObj.GetType() == typeof(DataGrid))
{
DataGrid myGrid = iObj as DataGrid;
ObservableCollection<DataGridColumn> Columns = (ObservableCollection<DataGridColumn>)iArgs.NewValue;
if (Columns != null)
{
myGrid.Columns.Clear();
if (Columns != null && Columns.Count > 0)
{
foreach (DataGridColumn dataGridColumn in Columns)
{
myGrid.Columns.Add(dataGridColumn);
}
}
Columns.CollectionChanged += delegate(object sender, NotifyCollectionChangedEventArgs args)
{
if (args.NewItems != null)
{
UserControl control = ((UserControl)((Grid)myGrid.Parent).Parent);
foreach (DataGridColumn column in args.NewItems.Cast<DataGridColumn>())
{
/// This is where I tried to fix the exception. ///
DataGridColumn temp = new DataGridTextColumn();
temp.Header = column.Header;
temp.SortMemberPath = column.SortMemberPath;
control.Dispatcher.Invoke(new Action(delegate()
{
myGrid.Columns.Add(temp);
}), DispatcherPriority.Normal);
////////////////////////////////////////////////////
}
}
if (args.OldItems != null)
{
foreach (DataGridColumn column in args.OldItems.Cast<DataGridColumn>())
{
myGrid.Columns.Remove(column);
}
}
};
}
}
}
public static ObservableCollection<DataGridColumn> GetColumns(DependencyObject iObj)
{
return (ObservableCollection<DataGridColumn>)iObj.GetValue(ColumnsProperty);
}
public static void SetColumns(DependencyObject iObj, ObservableCollection<DataGridColumn> iColumns)
{
iObj.SetValue(ColumnsProperty, iColumns);
}
}
The section where I put /// This is where I tried to fix the exception. /// is where the exception is getting thrown, exactly at myGrid.add(...);
The myGrid object does not allow me to add that column to be added to the collection of columns of the DataGrid. Which is why I surrounded it with a Dispatcher.Invoke. Strangely, if I execute myGrid.Columns.Add(new DataGridTextColumn()); it works and I can see the empty columns getting added in the view but myGrid.Columns.Add(temp); throws the exception.
There must be something I don't catch with this thing.
Please HELP!!!!
EDIT following Stipo suggestion
UserControl control = ((UserControl)((Grid)myGrid.Parent).Parent);
Columns.CollectionChanged += delegate(object sender, NotifyCollectionChangedEventArgs args)
{
control.Dispatcher.Invoke(new Action(delegate()
{
if (args.NewItems != null)
{
foreach (DataGridColumn column in args.NewItems.Cast<DataGridColumn>())
{
DataGridColumn temp = new DataGridTextColumn();
temp.Header = column.Header;
temp.SortMemberPath = column.SortMemberPath;
myGrid.Columns.Add(temp);
}
}
if (args.OldItems != null)
{
foreach (DataGridColumn column in args.OldItems.Cast<DataGridColumn>())
{
myGrid.Columns.Remove(column);
}
}
}), DispatcherPriority.Normal);
};
Move DataGridColumn creation code into the dispatcher delegate.
The issue happens because DataGridColumn inherits from DispatcherObject which has one field which says on which thread the DispatcherObject was created and when DataGridColumn is constructed this field will be set to your worker thread.
When that column gets added to DataGrid.Columns collection, exception will be thrown because DataGridColumn is not created on default GUI thread on which the DataGrid is created.
NEW SOLUTION
After playing around with your code, I have decided to implement different solution which should solve your problem and make your view model cleaner since it won't have GUI members (DataGridColumns) in it anymore.
New solution abstracts DataGridColumn in view model layer with ItemProperty class and DataGridExtension class takes care of converting ItemProperty instance to DataGridColumn instance in WPF's Dispatcher thread.
Here is a complete solution with test example (I recommend you create an empty WPF Application project and insert code in it to test the solution):
ItemProperty.cs
using System;
namespace WpfApplication
{
// Abstracts DataGridColumn in view-model layer.
class ItemProperty
{
public Type PropertyType { get; private set; }
public string Name { get; private set; }
public bool IsReadOnly { get; private set; }
public ItemProperty(Type propertyType, string name, bool isReadOnly)
{
this.PropertyType = propertyType;
this.Name = name;
this.IsReadOnly = isReadOnly;
}
}
}
DataGridExtension.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using System.Windows.Threading;
namespace WpfApplication
{
static class DataGridExtension
{
private static readonly DependencyProperty ColumnBinderProperty = DependencyProperty.RegisterAttached("ColumnBinder", typeof(ColumnBinder), typeof(DataGridExtension));
public static readonly DependencyProperty ItemPropertiesProperty = DependencyProperty.RegisterAttached(
"ItemProperties",
typeof(ObservableCollection<ItemProperty>),
typeof(DataGridExtension), new PropertyMetadata((d, e) =>
{
var dataGrid = d as DataGrid;
if (dataGrid != null)
{
var columnBinder = dataGrid.GetColumnBinder();
if (columnBinder != null)
columnBinder.Dispose();
var itemProperties = e.NewValue as ObservableCollection<ItemProperty>;
dataGrid.SetColumnBinder(new ColumnBinder(dataGrid.Dispatcher, dataGrid.Columns, itemProperties));
}
}));
[AttachedPropertyBrowsableForType(typeof(DataGrid))]
[DependsOn("ItemsSource")]
public static ObservableCollection<ItemProperty> GetItemProperties(this DataGrid dataGrid)
{
return (ObservableCollection<ItemProperty>)dataGrid.GetValue(ItemPropertiesProperty);
}
public static void SetItemProperties(this DataGrid dataGrid, ObservableCollection<ItemProperty> itemProperties)
{
dataGrid.SetValue(ItemPropertiesProperty, itemProperties);
}
private static ColumnBinder GetColumnBinder(this DataGrid dataGrid)
{
return (ColumnBinder)dataGrid.GetValue(ColumnBinderProperty);
}
private static void SetColumnBinder(this DataGrid dataGrid, ColumnBinder columnBinder)
{
dataGrid.SetValue(ColumnBinderProperty, columnBinder);
}
// Takes care of binding ItemProperty collection to DataGridColumn collection.
// It derives from TypeConverter so it can access SimplePropertyDescriptor class which base class (PropertyDescriptor) is used in DataGrid.GenerateColumns method to inspect if property is read-only.
// It must be stored in DataGrid (via ColumnBinderProperty attached dependency property) because previous binder must be disposed (CollectionChanged handler must be removed from event), otherwise memory-leak might occur.
private class ColumnBinder : TypeConverter, IDisposable
{
private readonly Dispatcher dispatcher;
private readonly ObservableCollection<DataGridColumn> columns;
private readonly ObservableCollection<ItemProperty> itemProperties;
public ColumnBinder(Dispatcher dispatcher, ObservableCollection<DataGridColumn> columns, ObservableCollection<ItemProperty> itemProperties)
{
this.dispatcher = dispatcher;
this.columns = columns;
this.itemProperties = itemProperties;
this.Reset();
this.itemProperties.CollectionChanged += this.OnItemPropertiesCollectionChanged;
}
private void Reset()
{
this.columns.Clear();
foreach (var column in GenerateColumns(itemProperties))
this.columns.Add(column);
}
private static IEnumerable<DataGridColumn> GenerateColumns(IEnumerable<ItemProperty> itemProperties)
{
return DataGrid.GenerateColumns(new ItemProperties(itemProperties));
}
private void OnItemPropertiesCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// CollectionChanged is handled in WPF's Dispatcher thread.
this.dispatcher.Invoke(new Action(() =>
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
int index = e.NewStartingIndex >= 0 ? e.NewStartingIndex : this.columns.Count;
foreach (var column in GenerateColumns(e.NewItems.Cast<ItemProperty>()))
this.columns.Insert(index++, column);
break;
case NotifyCollectionChangedAction.Remove:
if (e.OldStartingIndex >= 0)
for (int i = 0; i < e.OldItems.Count; ++i)
this.columns.RemoveAt(e.OldStartingIndex);
else
this.Reset();
break;
case NotifyCollectionChangedAction.Replace:
if (e.OldStartingIndex >= 0)
{
index = e.OldStartingIndex;
foreach (var column in GenerateColumns(e.NewItems.Cast<ItemProperty>()))
this.columns[index++] = column;
}
else
this.Reset();
break;
case NotifyCollectionChangedAction.Reset:
this.Reset();
break;
}
}));
}
public void Dispose()
{
this.itemProperties.CollectionChanged -= this.OnItemPropertiesCollectionChanged;
}
// Used in DataGrid.GenerateColumns method so that .NET takes care of generating columns from properties.
private class ItemProperties : IItemProperties
{
private readonly ReadOnlyCollection<ItemPropertyInfo> itemProperties;
public ItemProperties(IEnumerable<ItemProperty> itemProperties)
{
this.itemProperties = new ReadOnlyCollection<ItemPropertyInfo>(itemProperties.Select(itemProperty => new ItemPropertyInfo(itemProperty.Name, itemProperty.PropertyType, new ItemPropertyDescriptor(itemProperty.Name, itemProperty.PropertyType, itemProperty.IsReadOnly))).ToArray());
}
ReadOnlyCollection<ItemPropertyInfo> IItemProperties.ItemProperties
{
get { return this.itemProperties; }
}
private class ItemPropertyDescriptor : SimplePropertyDescriptor
{
public ItemPropertyDescriptor(string name, Type propertyType, bool isReadOnly)
: base(null, name, propertyType, new Attribute[] { isReadOnly ? ReadOnlyAttribute.Yes : ReadOnlyAttribute.No })
{
}
public override object GetValue(object component)
{
throw new NotSupportedException();
}
public override void SetValue(object component, object value)
{
throw new NotSupportedException();
}
}
}
}
}
}
Item.cs (used for testing)
using System;
namespace WpfApplication
{
class Item
{
public string Name { get; private set; }
public ItemKind Kind { get; set; }
public bool IsChecked { get; set; }
public Uri Link { get; set; }
public Item(string name)
{
this.Name = name;
}
}
enum ItemKind
{
ItemKind1,
ItemKind2,
ItemKind3
}
}
ViewModel.cs (used for testing)
using System;
using System.Collections.ObjectModel;
using System.Threading;
namespace WpfApplication
{
class ViewModel
{
public ObservableCollection<Item> Items { get; private set; }
public ObservableCollection<ItemProperty> ItemProperties { get; private set; }
public ViewModel()
{
this.Items = new ObservableCollection<Item>();
this.ItemProperties = new ObservableCollection<ItemProperty>();
for (int i = 0; i < 1000; ++i)
this.Items.Add(new Item("Name " + i) { Kind = (ItemKind)(i % 3), IsChecked = (i % 2) == 1, Link = new Uri("http://www.link" + i + ".com") });
}
private bool testStarted;
// Test method operates on another thread and it will first add all columns one by one in interval of 1 second, and then remove all columns one by one in interval of 1 second.
// Adding and removing will be repeated indefinitely.
public void Test()
{
if (this.testStarted)
return;
this.testStarted = true;
ThreadPool.QueueUserWorkItem(state =>
{
var itemProperties = new ItemProperty[]
{
new ItemProperty(typeof(string), "Name", true),
new ItemProperty(typeof(ItemKind), "Kind", false),
new ItemProperty(typeof(bool), "IsChecked", false),
new ItemProperty(typeof(Uri), "Link", false)
};
bool removing = false;
while (true)
{
Thread.Sleep(1000);
if (removing)
{
if (this.ItemProperties.Count > 0)
this.ItemProperties.RemoveAt(this.ItemProperties.Count - 1);
else
removing = false;
}
else
{
if (this.ItemProperties.Count < itemProperties.Length)
this.ItemProperties.Add(itemProperties[this.ItemProperties.Count]);
else
removing = true;
}
}
});
}
}
}
MainWindow.xaml (used for testing)
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<Window.DataContext>
<local:ViewModel/>
</Window.DataContext>
<DockPanel>
<Button DockPanel.Dock="Top" Content="Test" Click="OnTestButtonClicked"/>
<DataGrid ItemsSource="{Binding Items}" local:DataGridExtension.ItemProperties="{Binding ItemProperties}" AutoGenerateColumns="False"/>
</DockPanel>
</Window>
MainWindow.xaml.cs (used for testing)
using System.Windows;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnTestButtonClicked(object sender, RoutedEventArgs e)
{
((ViewModel)this.DataContext).Test();
}
}
}
WPF Extension (found in codeplex) has a extended version of ObservableCollection called DispatchedObservableCollection here , which ideal here. Its worth having a look at it and customize accordingly.

collection dependency properties

I have a custom control that has a DependencyProperty of type ObservableCollection that is bound to an observableCollection:
<MyControl MyCollectionProperty = {Binding MyObservableCollection} ...
Problem is adding to MyObservableCollection does not update MyCollectionProperty.
I need to completly replace the MyObservableCollection to make it work e.g.
MyObservableCollection = null;
MyObservableCollection = new ObservableCollection(){...}
Is there a better way to deal with this?
EDIT:
public ObservableCollection<string> Columns
{
get { return (ObservableCollection<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
In addition to what grantz has answered, I would suggest to declare the property with type IEnumerable<string> and check at runtime if the collection object implements the INotifyCollectionChanged interface. This provides greater flexibility as to which concrete collection implementation may be used as property value. A user may then decide to have their own specialized implementation of an observable collection.
Note also that in the ColumnsPropertyChanged callback the CollectionChanged event handler is attached to the new collection, but also removed from the old one.
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns", typeof(IEnumerable<string>), typeof(MyControl),
new PropertyMetadata(null, ColumnsPropertyChanged));
public IEnumerable<string> Columns
{
get { return (IEnumerable<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
private static void ColumnsPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control= (MyControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.ColumnsCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.ColumnsCollectionChanged;
}
control.UpdateColumns();
}
private void ColumnsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// optionally take e.Action into account
UpdateColumns();
}
private void UpdateColumns()
{
...
}
Below is a working example that may help.
In this example, the method OnChanged is called immediately, when the Add button is clicked "Changed" is written to the console.
The Control
public class MyControl : Control
{
public ObservableCollection<string> ExtraColumns
{
get { return (ObservableCollection<string>)GetValue(ExtraColumnsProperty); }
set { SetValue(ExtraColumnsProperty, value); }
}
public static readonly DependencyProperty ExtraColumnsProperty =
DependencyProperty.Register("ExtraColumns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as MyControl).OnChanged();
}
void OnChanged()
{
if ( ExtraColumns != null )
ExtraColumns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ExtraColumns_CollectionChanged);
}
void ExtraColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("Changed");
}
}
The Window
<Window x:Class="WpfApplication18.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyControl ExtraColumns="{Binding Extras}"/>
<Button Click="Button_Click">Add</Button>
</StackPanel>
</Window>
Window Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<string> _extras = new ObservableCollection<string>( );
public ObservableCollection<string> Extras
{
get { return _extras; }
set
{
if (value != _extras)
{
_extras = value;
}
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Extras.Add("Additional");
}
}

wpf custom control get/set not firing

I'm learning WPF and, coming from Flex and AS, it seems overly complicated at times. Opinions aside, my problem is the following.
I've created a custom control, ToolBarButton which is basically an image button that is destined to be included in a custom toolbar. I've added some properties to this control and I'd like to be able to set them from the XAML. Though the property appears in AutoCompletion on the XAML side, the Set method is never fired and the property stays null. So here's the ToolBarButton Code Behind :
public static readonly DependencyProperty ImgSrcProperty = DependencyProperty.RegisterAttached("ImgSource", typeof(string), typeof(ToolBarButton));
public static readonly DependencyProperty OnClickProperty = DependencyProperty.Register("OnClick", typeof(RoutedEventHandler), typeof(ToolBarButton));
public ToolBarButton(RoutedEventHandler OnClick, string imgSrc, Map map = null, string ConfigFile = null) :
base(ConfigFile, map)
{
if (OnClick != null) SetValue(OnClickProperty, OnClick);
if (imgSrc != null) SetValue(ImgSrcProperty, imgSrc);
this.AddChild(CreateButton());
InitializeComponent();
}
public ToolBarButton() : this(null, null) { }
private Button CreateButton()
{
BitmapImage icon = new BitmapImage();
icon.BeginInit();
icon.UriSource = new Uri(ImgSource, UriKind.Relative);
icon.EndInit();
Image img = new Image();
img.Stretch = Stretch.Fill;
img.Source = icon;
Button BtnToAdd = new Button();
BtnToAdd.Width = 35;
BtnToAdd.Height = 35;
BtnToAdd.Content = img;
BtnToAdd.Background = new ImageBrush(icon);
BtnToAdd.Click += OnClick;
return BtnToAdd;
}
public string ImgSource
{
get { return (string)GetValue(ImgSrcProperty); }
set { SetValue(ImgSrcProperty, value); }
}
public RoutedEventHandler OnClick
{
get { return (RoutedEventHandler)GetValue(OnClickProperty); }
set { SetValue(OnClickProperty, value); }
}
You'll notice two constructors, one to create the control at runtime, the other to create it from XAML.
And here's the XAML code that uses the custom control but doesn't fire the set method :
<BaseControls:ToolBar
x:Class="Basic_Mapping.Widgets.NavigationToolBar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:BaseControls="clr-namespace:Basic_Mapping.Base_Controls"
mc:Ignorable="d" >
<BaseControls:ToolBarButton Width="35" Height="35" ImgSource="Assets/i_zoomin.png" ConfigFileName="ZoomIn.xml" />
Any help would be appreciated!
Ggilmann
EDIT :
Here's the Base Class used for the ToolBarButton, it also has the same problem with it's properties.
public partial class ConfigurableUserControl : UserControl
{
private XmlDocument configXML;
public static readonly DependencyProperty XmlProperty = DependencyProperty.Register("ConfigFileName", typeof(string), typeof(ConfigurableUserControl));
public static readonly DependencyProperty MapProperty = DependencyProperty.Register("Map", typeof(Map), typeof(ConfigurableUserControl));
public ConfigurableUserControl(string configFile, Map map)
{
if (configFile != null) SetValue(XmlProperty, configFile);
if (map != null) SetValue(MapProperty, map);
string file = (string)GetValue(XmlProperty);
if (file != null)
{
configXML = new XmlDocument();
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "..\\..\\Config\\" + configFile);
if (File.Exists(path)) configXML.Load(path);
}
}
public ConfigurableUserControl() : this(null, null) { }
public string ConfigFileName
{
//get { return (string)GetValue(XmlProperty); }
set { SetValue(XmlProperty, value); }
}
public Map Map
{
get { return (Map)GetValue(MapProperty); }
set { SetValue(MapProperty, value); }
}
public XmlDocument ConfigXML
{
get { return configXML; }
}
}
My guess is that this problem, and your problems with the base class, are due to the fact that you're not implementing INotifyPropertyChanged and making the appropriate calls.
Try putting InitializeComponent(); at the beginning of the constructor as opposed to at the end where it currently is.

.NET Propertygrid refresh trouble

Property grid do not show new value of selected object.
For example:
o.val = "1";
pg.SelectedObject = o;
o.val = "2";
pg.Refresh();
The property in property grid is still "1";
It is changing only if you click on this property.
Or like that:
o.val = "1";
pg.SelectedObject = o;
o.val = "2";
pg.SelectedObject = o;
but in this case focus will be changed to PropertyGrid.
As I told you in my comment, your code is not enough to understand your issue. Presented like this it should work. Here is mine that works well:
public class Target
{
private int _myInt = 1;
public int MyInt { set; get; }
public static Target target = new Target();
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Button button = new Button()
{
Text = "Click me",
Dock = DockStyle.Bottom
};
Form form = new Form
{
Controls = {
new PropertyGrid {
SelectedObject = Target.target,
Dock = DockStyle.Fill,
},
button
}
};
button.Click += new EventHandler(button_Click);
Application.Run(form);
}
static void button_Click(object sender, EventArgs e)
{
Target.target.MyInt = 2;
Form form = Form.ActiveForm;
(form.Controls[0] as PropertyGrid).Refresh();
}
}
The call to Refresh() actually rebuilds the grid. Remove it and you will see the change only when you click the property.
Cause you just not gave some code, here is a working example.
Just put a Button and a PropertyGrid onto a form.
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication
{
public partial class FormMain : Form
{
Random rand;
MyObject obj;
public FormMain()
{
InitializeComponent();
rand = new Random();
obj = new MyObject();
propertyGrid1.SelectedObject = obj;
}
private void button1_Click(object sender, EventArgs e)
{
obj.MyValue = rand.Next();
obj.IsEnabled = !obj.IsEnabled;
obj.MyText = DateTime.Now.ToString();
propertyGrid1.Refresh();
}
}
public class MyObject : INotifyPropertyChanged
{
private int _MyValue;
public int MyValue
{
get
{
return _MyValue;
}
set
{
_MyValue = value;
NotifyPropertyChanged("MyValue");
}
}
private string _MyText;
public string MyText
{
get
{
return _MyText;
}
set
{
_MyText = value;
NotifyPropertyChanged("MyText");
}
}
private bool _IsEnabled;
public bool IsEnabled
{
get
{
return _IsEnabled;
}
set
{
_IsEnabled = value;
NotifyPropertyChanged("IsEnabled");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
}

Categories

Resources