Abstract DomainCollectionView (Entity Framework) code into its own service (MVVM Silverlight4) - c#

I've got a VM class below that's being used to wire up a view to my ADO.NET Entity Data Model, utilizing a P_BUDGET class. This works fine, gets my data, makes everything pretty. I have about 15-20 pages that are all going to be based on the same structure as this, and the code is going to be virtually identical except for the EntityType (P_BUDGET, P_ACCOUNT, P_CIRCUIT, etc etc). I feel like there should be a way to abstract this out, but I tried, and failed miserably! I also feel like I should be able to use one view, one viewmodel, and just swap out the entities binding to the GV... I just haven't been able to find a way to variablize the type, which permeates the entire viewmodel.
Appreciate your help,
Scott
public class TestViewModel : ViewModelBase
{
private readonly ODADomainContext _context = new ODADomainContext();
private readonly DomainCollectionView<P_BUDGET> _view;
private readonly DomainCollectionViewLoader<P_BUDGET> _loader;
private readonly EntityList<P_BUDGET> _source;
private bool _isGridEnabled;
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
this._source = new EntityList<P_BUDGET>(this._context.P_BUDGETs);
this._loader = new DomainCollectionViewLoader<P_BUDGET>(
this.LoadSampleEntities,
this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<P_BUDGET>(this._loader, this._source);
INotifyCollectionChanged notifyingSortDescriptions =
(INotifyCollectionChanged)this.CollectionView.SortDescriptions;
notifyingSortDescriptions.CollectionChanged +=
(sender, e) => this._view.MoveToFirstPage();
using (this.CollectionView.DeferRefresh())
{
this._view.PageSize = 10;
this._view.MoveToFirstPage();
}
}
#region View Properties
public bool IsGridEnabled
{
get
{
return this._isGridEnabled;
}
private set
{
if (this._isGridEnabled != value)
{
this._isGridEnabled = value;
this.RaisePropertyChanged("IsGridEnabled");
}
}
}
public ICollectionView CollectionView
{
get { return this._view; }
}
#endregion
private LoadOperation<P_BUDGET> LoadSampleEntities()
{
this.IsGridEnabled = false;
return this._context.Load(
this._context.GetBudgetsQuery());
}
private void OnLoadSampleEntitiesCompleted(LoadOperation<P_BUDGET> op)
{
this.IsGridEnabled = true;
if (op.HasError)
{
// TODO: handle errors
_view.PageSize = 0;
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this._source.Source = op.Entities;
_view.PageSize = 10;
this._view.MoveToFirstPage();
if (op.TotalEntityCount != -1)
{
this._view.SetTotalItemCount(op.TotalEntityCount);
}
}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}

Try something like this. This is not tested (obviously), not even complied. This also assumes that the EntityTypes (P_BUDGET, P_ACCOUNT, P_CIRCUIT etc.) are not POCOs.
public class TestViewModel<TEntity> : ViewModelBase
{
private readonly ODADomainContext _context = new ODADomainContext();
private readonly DomainCollectionView<TEntity> _view;
private readonly DomainCollectionViewLoader<TEntity> _loader;
private readonly EntityList<TEntity> _source;
private bool _isGridEnabled;
/// <summary>
/// Initializes a new instance of the TestViewModel class.
/// </summary>
public TestViewModel()
{
this._source = new EntityList<TEntity>(this._context.GetEntitySet<TEntity>);
this._loader = new DomainCollectionViewLoader<TEntity>(
this.LoadSampleEntities,
this.OnLoadSampleEntitiesCompleted);
this._view = new DomainCollectionView<TEntity>(this._loader, this._source);
INotifyCollectionChanged notifyingSortDescriptions =
(INotifyCollectionChanged)this.CollectionView.SortDescriptions;
notifyingSortDescriptions.CollectionChanged +=
(sender, e) => this._view.MoveToFirstPage();
using (this.CollectionView.DeferRefresh())
{
this._view.PageSize = 10;
this._view.MoveToFirstPage();
}
}
#region View Properties
public bool IsGridEnabled
{
get
{
return this._isGridEnabled;
}
private set
{
if (this._isGridEnabled != value)
{
this._isGridEnabled = value;
this.RaisePropertyChanged("IsGridEnabled");
}
}
}
public ICollectionView CollectionView
{
get { return this._view; }
}
#endregion
private LoadOperation<TEntity> LoadSampleEntities()
{
this.IsGridEnabled = false;
return this._context.Load(
this._context.GetBudgetsQuery());
}
private void OnLoadSampleEntitiesCompleted(LoadOperation<TEntity> op)
{
this.IsGridEnabled = true;
if (op.HasError)
{
// TODO: handle errors
_view.PageSize = 0;
op.MarkErrorAsHandled();
}
else if (!op.IsCanceled)
{
this._source.Source = op.Entities;
_view.PageSize = 10;
this._view.MoveToFirstPage();
if (op.TotalEntityCount != -1)
{
this._view.SetTotalItemCount(op.TotalEntityCount);
}
}
}
////public override void Cleanup()
////{
//// // Clean own resources if needed
//// base.Cleanup();
////}
}
// http://blog.zoolutions.se/post/2010/04/05/Generic-Repository-for-Entity-Framework-for-Pluralized-Entity-Set.aspx
public static class ObjectContextExtensions
{
internal static EntitySetBase GetEntitySet<TEntity>(this ObjectContext context)
{
EntityContainer container = context.MetadataWorkspace.GetEntityContainer(context.DefaultContainerName, DataSpace.CSpace);
Type baseType = GetBaseType(typeof(TEntity));
EntitySetBase entitySet = container.BaseEntitySets
.Where(item => item.ElementType.Name.Equals(baseType.Name))
.FirstOrDefault();
return entitySet;
}
private static Type GetBaseType(Type type)
{
var baseType = type.BaseType;
if (baseType != null && baseType != typeof(EntityObject))
{
return GetBaseType(type.BaseType);
}
return type;
}
}

Related

Interaction with the collection does not use the set accessor [duplicate]

This question already has answers here:
List<> collection does not update the View in MVVM
(4 answers)
Closed 1 year ago.
public class MainWindowVM : ViewModel
{
#region public members
public User LocalUser
{
get => Settings.LocalUser;
set
{
Settings.LocalUser = value;
OnPropertyChanged();
}
}
public WebUser SelectedUser
{
get => selectedUser;
set
{
selectedUser = value;
OnPropertyChanged();
}
}
public string InputMessage
{
get => inputMessage;
set
{
inputMessage = value;
OnPropertyChanged();
}
}
public List<Message> MessageList
{
get
{
if (selectedUser == null)
return null;
return SelectedUser.MessageThread;
}
set
{
SelectedUser.MessageThread = value;
OnPropertyChanged();
}
}
public RelayCommand SendMessageCommand
{
get
{
return sendMessageCommand ??
(sendMessageCommand = new RelayCommand(obj =>
{
SendMessage();
}));
}
}
#endregion
#region public methods
public MainWindowVM(Window window)
{
Window MainWindow = window;
MawLib.WindowResizer windowResizer = new MawLib.WindowResizer(window);
Settings.InitializeInstance();
SelUser = SelectUser;
UserListRaw = new List<WebUser>
{
new WebUser(new UserId("SomeTestUser23")),
new WebUser(new UserId("NoName"))
};
WebClient.Connect();
}
public void SelectUser(WebUser user)
{
SelectedUser = user;
MessageList = SelectedUser.MessageThread;
}
#endregion
#region private members
private WebUser selectedUser;
private string inputMessage;
#endregion
#region private methods
private void SendMessage()
{
if (InputMessage != "" && InputMessage != null)
{
MessageList.Add(new Message(InputMessage, LocalUser, SelectedUser));
WebClient.SendRequest(new Message(InputMessage, Settings.LocalUser, selectedUser), Request.RequestType.Message);
InputMessage = "";
}
}
#endregion
}
Need to have a Message List.Add interacted via the Message List property, but for some reason it bypasses it. I also tried to access the accessor via assignment, but this also bypassed the property. I was looking for information about this, but I didn't find anything suitable, maybe I was looking badly?
As suggested by Dzianis Karpuk, need to use ObservableCollection instead of List, everything works as it should.

Child properties update calling it's parent's `OnPropertyChanged`

I'm trying to create a XF component whose some properties are of a type that inherits from BindableObject. For illustrating, I have class Shadow with double Radius and Color ShadowColor properties and a class MyBoxText, that have a bool IsLoading and a Shadow Ghost properties.
My View and it's Bindings is working as expected, but I have an issue with it's custom renderer:
When I change the Ghost properties I need to redraw the entire view (of the MyBoxText control), to visually update the shadow color, for example.
Here's some mcve:
Classes code:
public class MyBoxText : Label /* It's bindable by inheritance */
{
#region Properties
public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
public Shadow Ghost
{
get { return (Shadow)GetValue(GhostProperty); }
set { SetValue(GhostProperty, value); }
}
#endregion
}
public class Shadow : BindableObject /* It's explictly bindable */
{
#region Properties
public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black) ;
public Color ShadowColor
{
get { return (Color)GetValue(ShadowColorProperty); }
set { SetValue(ShadowColorProperty, value); }
}
public static readonly BindableProperty ShadowRadiusProperty = BindableProperty.Create(nameof(ShadowRadius), typeof(double), typeof(Shadow), 20) ;
public double ShadowRadius
{
get { return (double)GetValue(ShadowRadiusProperty); }
set { SetValue(ShadowRadiusProperty, value); }
}
#endregion
public Shadow()
{
}
}
My renderer's code is like this:
public class MyBoxText : LabelRenderer
{
public MyBoxText()
{
SetWillNotDraw(false);
}
public override void Draw(Canvas canvas)
{
MyBoxText myView = (MyBoxText)this.Element;
// Some drawing logic
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == MyBoxText.IsLoadingProperty.PropertyName ||
e.PropertyName == MyBoxText.GhostProperty.PropertyName )
Invalidate();
}
}
The issue is that when I change the Ghost.ShadowColor property my 'OnElementPropertyChanged' override is not called, and the View stays with the old color on the screen.
Is there a way to propagate the child's 'Property Update' event to parent view 'Property Changed' or another way to achieve this?
The issue is that when I change the Ghost.ShadowColor property my 'OnElementPropertyChanged' override is not called, and the View stays with the old color on the screen.
Is there a way to propagate the child's 'Property Update' event to parent view 'Property Changed' or another way to achieve this?
Yes, there is a way. Since your Shadow inherits from BindableObject, which implements the INotifyPropertyChanged Interface. You can set notify ShadowColor change:
Add OnPropertyChanged() to Setter of ShadowColor in Shadow.cs:
public class Shadow : BindableObject /* It's explictly bindable */
{
#region Properties
public static readonly BindableProperty ShadowColorProperty = BindableProperty.Create(nameof(ShadowColor), typeof(Color), typeof(Shadow), Color.Black);
public Color ShadowColor
{
get { return (Color)GetValue(ShadowColorProperty); }
set { SetValue(ShadowColorProperty, value);
//Notify the ShadowColorProperty Changed
OnPropertyChanged();
}
}
...
}
Modify your MyBoxText.cs like this:
public class MyBoxText : Label /* It's bindable by inheritance */
{
...
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null);
public Shadow Ghost
{
get { return (Shadow)GetValue(GhostProperty); }
set {
//register the ShadowColor change event
value.PropertyChanged += ShadowColor_PropertyChanged;
SetValue(GhostProperty, value); }
}
private void ShadowColor_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
//unregister the event
this.Ghost.PropertyChanged -= ShadowColor_PropertyChanged;
//set this.Ghost to a new object with new ShadowColor to trigger the OnPropertyChanged
this.Ghost = new Shadow
{
ShadowColor = (sender as Shadow).ShadowColor,
ShadowRadius = Ghost.ShadowRadius
};
}
}
Thanks to Elvis's answer I got it. Based on his idea I've made some changing to reuse it on other components and I'm sharing it now just in case someone else needs something like this.
I thought that use it this way we could get a cleaner and simple code:
public class MyBoxText : Label /* It's bindable by inheritance */
{
// Added this as private property
private ChangingPropagator changingPropagator;
private ChangingPropagator ChangingPropagator
{
get
{
if (changingPropagator == null)
changingPropagator = new ChangingPropagator(this, OnPropertyChanged, nameof(Shadow.ShadowColor), nameof(Shadow.ShadowRadius));
return changingPropagator;
}
}
#region Properties
public static readonly BindableProperty IsLoadingProperty = BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(MyBoxText), false) ;
public bool IsLoading
{
get { return (bool)GetValue(IsLoadingProperty); }
set { SetValue(IsLoadingProperty, value); }
}
public static readonly BindableProperty GhostProperty = BindableProperty.Create(nameof(Ghost), typeof(Shadow), typeof(MyBoxText), null) ;
public Shadow Ghost
{
// Here I use the ChangingPropagator's Getter and Setter instead of the deafult ones:
get { return ChangingPropagator.GetValue<Shadow>(GhostProperty); }
set { ChangingPropagator.SetValue(GhostProperty,ref value); }
}
#endregion
}
And it's the ChangingPropagator class:
public class ChangingPropagator
{
string[] listenedProperties = new string[0];
Action<string> changesNotifyer = null;
BindableObject propagationRootObject = null;
List<KeyValuePair<string, object>> propagationProperties = new List<KeyValuePair<string, object>>();
public ChangingPropagator(BindableObject bindableObject, Action<string> onPropertyChangedMethod, params string[] propertyToListenTo)
{
changesNotifyer = onPropertyChangedMethod;
propagationRootObject = bindableObject;
listenedProperties = propertyToListenTo ?? listenedProperties;
// ToDo: Add some consistency checks
}
public void AddPropertyToListenTo(params string[] propertyName)
{
listenedProperties = listenedProperties.Union(propertyName).ToArray();
}
// I need handle it here too 'cause when I use the child `Ghost` property coming from XAML binding, it didn't hit the `set` method
public T GetValue<T>(BindableProperty property)
{
var value = propagationRootObject?.GetValue(property);
if (value != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
{
bindableSubObject.PropertyChanged -= PropagatorListener;
bindableSubObject.PropertyChanged += PropagatorListener;
if (!propagationProperties.Any(a => a.Key == property.PropertyName))
propagationProperties.Add(new KeyValuePair<string, object>(property.PropertyName, value));
}
}
return (T)value;
}
public void SetValue<T>(BindableProperty property, ref T value)
{
var oldValue = propagationRootObject?.GetValue(property);
if (oldValue != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
bindableSubObject.PropertyChanged -= PropagatorListener;
}
if (value != null)
{
INotifyPropertyChanged bindableSubObject = (value as INotifyPropertyChanged);
if (bindableSubObject != null)
{
bindableSubObject.PropertyChanged += PropagatorListener;
propagationProperties.RemoveAll(p => p.Key == property.PropertyName);
propagationProperties.Add(new KeyValuePair<string, object>(property.PropertyName, value));
}
}
propagationRootObject.SetValue(property, value);
}
private void PropagatorListener(object sender, PropertyChangedEventArgs e)
{
if (listenedProperties?.Contains(e.PropertyName) ?? true)
PropagationThrower(sender);
}
private void PropagationThrower(object sender)
{
if (propagationProperties.Any(p => p.Value == sender))
{
var prop = propagationProperties.FirstOrDefault(p => p.Value == sender);
changesNotifyer?.Invoke(prop.Key);
}
}
}

WPF/C# entirely programmatically binding an array of objects to a static ObservableCollection

Please assume this entire question deals in code, without any XAML.
I have a static ObservableCollection named myStaticList. It's a part of a non-static class named myClass.
public class myClass
{
public static ObservableCollection<CheckBoxStructure> myStaticList { get; set; }
static myClass()
{
myStaticList = new ObservableCollection<CheckBoxStructure>();
}
}
And the definition of CheckBoxStructure:
public class CheckBoxStructure
{
public string Description { get; set; }
public bool IsSelected { get; set; }
}
In addition, there's an array of checkboxes called checkBoxArray[], holding 3 elements. each checkbox has as content a textbox.
What I want to do is programmatically bind (two-way) these two, in such a manner that the IsChecked property of the checkboxes in the checkBoxArray[] array will bind to the IsSelected property of the myStaticList's CheckBoxStructure, and similarly so between the text of the textboxes inthe checkboxes' content and the Description property of the myStaticList's CheckBoxStructure.
In addition, I would like to avoid using loops, since it is preferable that this two lists will update each other if they change in size.
How is this possible?
Thanks!
Using XAML, an easy way would be to the declare an ItemsControl and a DataTemplate for it so that you can have a UserControl (CheckBox and TextBox inside) with its DataContext being a CheckBoxStructure. This way the bindings work between CheckBox.IsChecked and IsSelected property and between TextBox.Text and Description property.
If you need to this only in code then you would have to create same behavior (ItemsControl with a DataTemplate). You have at least 2 options
1.
DataTemplate template = new DataTemplate();
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
template.VisualTree = factory;
FrameworkElementFactory childFactory = new FrameworkElementFactory(typeof(CheckBox));
childFactory.SetBinding(CheckBox.IsChecked, new Binding("IsSelected"));
factory.AppendChild(childFactory);
childFactory = new FrameworkElementFactory(typeof(TextBox));
childFactory.SetBinding(Label.ContentProperty, new Binding("Description"));
factory.AppendChild(childFactory);
2.
MemoryStream sr = null;
ParserContext pc = null;
string xaml = string.Empty;
xaml = "<DataTemplate><StackPanel><TextBlock Text="{Binding Description"/><CheckBox IsChecked="{Binding IsSelected"/></StackPanel></DataTemplate>";
sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
pc = new ParserContext();
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
DataTemplate datatemplate = (DataTemplate)XamlReader.Load(sr, pc);
this.Resources.Add("dt", datatemplate);
Later edit, after discussion from comments; this example works only one way of binding but is easily to make it two ways. Please note that this is only a trivial example of a concept and is not complete: you need to modify the list classes to suit how you wish for objects to be paired, you may need to add more guards for corner cases, you may need to make it thread safe and so on...
First the basic binding objects:
class Binder
{
public Binder()
{
_bindings = new Dictionary<string, List<string>>();
}
private INotifyPropertyChanged _dataContext;
public INotifyPropertyChanged DataContext
{
get { return _dataContext; }
set
{
if (_dataContext != null)
{
_dataContext.PropertyChanged -= _dataContext_PropertyChanged;
}
_dataContext = value;
_dataContext.PropertyChanged += _dataContext_PropertyChanged;
}
}
void _dataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_bindings.ContainsKey(e.PropertyName))
{
var bindableType = _dataContext.GetType();
var bindableProp = bindableType.GetProperty(e.PropertyName);
if (bindableProp == null)
{
return;
}
var binderType = this.GetType();
foreach (var binderPropName in _bindings[e.PropertyName])
{
var binderProp = binderType.GetProperty(binderPropName);
if (binderProp == null)
{
continue;
}
var value = bindableProp.GetValue(_dataContext);
binderProp.SetValue(this, value);
}
}
}
Dictionary<string, List<string>> _bindings;
public void AddBinding(string binderPropertyName, string bindablePropertyName)
{
if (!_bindings.ContainsKey(bindablePropertyName))
{
_bindings.Add(bindablePropertyName, new List<string>());
}
_bindings[bindablePropertyName].Add(bindablePropertyName);
}
}
class Bindable : INotifyPropertyChanged
{
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then the holding lists for them:
class BindableList<T> : List<T> where T : Bindable
{
public event Action<T> ItemAdded;
public new void Add(T item)
{
base.Add(item);
NotifyItemAdded(item);
}
private void NotifyItemAdded(T item)
{
if (ItemAdded != null)
{
ItemAdded(item);
}
}
}
class BinderList<T> : List<T> where T : Binder
{
public BinderList()
{
_bindingRules = new Dictionary<string, string>();
}
private BindableList<Bindable> _dataContextList;
public BindableList<Bindable> DataContextList
{
get { return _dataContextList; }
set
{
if (_dataContextList != null)
{
_dataContextList.ItemAdded -= _dataContextList_ItemAdded;
}
_dataContextList = value;
_dataContextList.ItemAdded += _dataContextList_ItemAdded;
}
}
void _dataContextList_ItemAdded(Bindable obj)
{
foreach (var pair in _bindingRules)
{
this[Count-1].AddBinding(pair.Key, pair.Value);
this[Count - 1].DataContext = obj;
}
}
private Dictionary<string, string> _bindingRules;
public void AddBindingRule(string binderPropertyName, string bindablePropertyName)
{
_bindingRules.Add(binderPropertyName, bindablePropertyName);
}
}
Now the actual classes with properties:
class BinderElement : Binder
{
private string _description;
public string Description
{
get { return _description; }
set { _description = value; }
}
}
class BindableElement : Bindable
{
private string _description;
public string Description
{
get
{
return _description;
}
set
{
_description = value;
NotifyPropertyChanged("Description");
}
}
}
And an example to use them:
static void Main(string[] args)
{
var bindableList = new BindableList<Bindable>();
var binderList = new BinderList<BinderElement>()
{
new BinderElement(),
new BinderElement()
};
binderList.DataContextList = bindableList;
binderList.AddBindingRule("Description", "Description");
bindableList.Add(new BindableElement());
bindableList.Add(new BindableElement());
((BindableElement)bindableList[1]).Description = "This should arrive in BinderElement Description property";
Console.WriteLine(binderList[1].Description);
Console.ReadLine();
}

WPF TextBox Search

I have a quickFind TextBox. I want to filter a collection for records that contain the quickFind string.
How can I delay the search until the user has stopped typing for 2 seconds?
Here's the ReactiveUI way to do the whole thing (filtering the items after a 2sec delay):
// These are defined in your ViewModel class as settable Properties
string FilterText;
ReactiveList<Record> ListOfRecords;
IReactiveDerivedList<Record> FilteredRecords;
// This is in your ViewModel constructor
FilteredRecords = ListOfRecords.CreateDerivedCollection(
x => !String.IsNullOrWhiteSpace(FilterText) ? recordContainsString(FilterText) : true,
x => x.Id,
this.WhenAnyValue(x => x.FilterText).Throttle(TimeSpan.FromSeconds(2.0));
If all you want to do is find out when a property has changed but after an idle time, it's:
this.WhenAnyValue(x => x.SomeProperty)
.Throttle(TimeSpan.FromSeconds(2.0), RxApp.MainThreadScheduler)
.Subscribe(x => Console.WriteLine("The item is " + x);
I love the Timer and Lock example because it shows how much easier ReactiveUI is :)
below is a class that I'm hoping does the trick for you. including sample usage shown at the bottom.
public class EventDelayer
{
/// <summary>
/// Contains info on an individual event that was queued;
/// </summary>
public class DelayedEventInfo
{
private readonly object _sender;
private readonly EventArgs _eventArgs;
private readonly DateTime _eventTime;
public DelayedEventInfo(object sender, EventArgs eventArgs, DateTime eventTime)
{
_sender = sender;
_eventArgs = eventArgs;
_eventTime = eventTime;
}
public object Sender { get { return _sender; } }
public EventArgs EventArgs { get { return _eventArgs; } }
public DateTime EventTime { get { return _eventTime; } }
}
/// <summary>
/// contains a list of
/// </summary>
public class DelayedEventArgs : EventArgs, IEnumerable<DelayedEventInfo>
{
private readonly List<DelayedEventInfo> _eventInfos;
public DelayedEventArgs(IEnumerable<DelayedEventInfo> eventInfos)
{
_eventInfos = new List<DelayedEventInfo>(eventInfos);
}
public IEnumerator<DelayedEventInfo> GetEnumerator()
{
return _eventInfos.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _eventInfos.GetEnumerator();
}
}
private readonly List<DelayedEventInfo> _infoList = new List<DelayedEventInfo>();
private readonly TimeSpan _delayTime;
private readonly object _lock = new object();
private System.Threading.Timer _timer;
public event EventHandler<DelayedEventArgs> DelayedEvent;
public EventDelayer(TimeSpan delayTime)
{
_delayTime = delayTime;
}
/// <summary>
/// call to 'enqueue' an event.
/// </summary>
public void Enqueue(object sender, EventArgs args)
{
lock (_lock)
{
_infoList.Add(new DelayedEventInfo(sender, args, DateTime.Now));
if (_timer != null)
{
_timer.Dispose();
_timer = null;
}
_timer = new System.Threading.Timer(ThreadProc, this, _delayTime, TimeSpan.FromMilliseconds(-1));
}
}
/// <summary>
/// raises the event.
/// </summary>
private void HandleTimer()
{
lock (_lock)
{
var ev = this.DelayedEvent;
if (ev != null)
{
DelayedEventArgs args = new DelayedEventArgs(_infoList);
Invoke(()=> ev(this, args));
}
_infoList.Clear();
}
}
private static void ThreadProc(Object stateInfo)
{
EventDelayer thisObj = (EventDelayer)stateInfo;
thisObj.HandleTimer();
}
private static Lazy<System.Windows.Threading.Dispatcher> _dispatchObject = new Lazy<System.Windows.Threading.Dispatcher>(() =>
{
if (Application.Current != null)
{
return Application.Current.Dispatcher;
}
else
{
return null;
}
});
public static void Invoke(Action action)
{
if (_dispatchObject.Value == null || _dispatchObject.Value.CheckAccess())
{
action();
}
else
{
_dispatchObject.Value.Invoke(action);
}
}
}
private class ExampleUsage
{
/// <summary>
/// shows how to create a event delayer and use it to listen to the events from a text box and call if no further changes for 2 seconds.
/// </summary>
private static void ShowUsage(System.Windows.Controls.TextBox textBox)
{
EventDelayer eventDelayer = new EventDelayer(TimeSpan.FromSeconds(2));
textBox.TextChanged += eventDelayer.Enqueue;
eventDelayer.DelayedEvent += eventDelayer_DelayedEvent;
}
/// <summary>
/// redo search here. if required you can access the event args originally raised from the textbox through the event args of this method
/// </summary>
static void eventDelayer_DelayedEvent(object sender, EventDelayer.DelayedEventArgs e)
{
foreach (var eventInfo in e)
{
var originalSender = eventInfo.Sender;
var args = eventInfo.EventArgs;
var timeInitiallyCalled = eventInfo.EventTime;
}
}
}
Bind the textbox text to a string, then set a delay in the binding
<TextBox>
<TextBox.Text>
<Binding Path="searchText" UpdateSourceTrigger="PropertyChanged" Delay="2000" />
</TextBox.Text>
</TextBox>

Refresh an ObservableCollection (overwrite)

How can I refresh the following ObservableCollection?
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties;
public ObservableCollection<ServerProperties> Properties
{
get
{
properties = new ObservableCollection<ServerProperties>();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
return properties;
}
}
}
public class ServerProperties
{
private string property;
private string value;
public ServerProperties()
{
}
public ServerProperties(string property, string value)
{
Property = property;
Value = value;
}
public string Property
{
get
{
return this.property;
}
set
{
this.property = value;
}
}
public string Value
{
get
{
return this.value;
}
set
{
this.value = value;
}
}
public override string ToString()
{
return string.Format("[Property : {0}]", Value);
}
}
I changed the value of textProperties[,] and now I'd like to overwrite the previous content of the collection with the current content of textProperties[,].
What would be the simplest way to do this?
Any help would be appreciated.
Start off by implementing INotifyPropertyChanged in your ViewModel as well as in the ServerProperties object. This way you can raise the PropetyChanged event which will pass back to the user interface.
ViewModel
public class ViewModelProperties : INotifyPropertyChanged {
public event ProeprtyChangedEventHandler PropertyChanged;
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties {
get { return properties;}
set {
properties = value;
this.RaisePropertyChangedEvent("Properties");
}
}
private void RaisePropertyChanged(string propertyName) {
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Implementing this on the ServerProperties object as well will allow you to change the objects, at any level, and have it bubble up to the interface.
Lastly look into your population code and in order to get the property to update successfully first populate it to a List then re-initialise the ObservableCollection using the List.
Properties = new ObservableCollection<ServerProperties>(propertiesList);
This also allows you to better handle the creation of your ObservableCollection and perform tests before posting the output to the interface. Hope it helps.
For example, one of the simpler solutions could be
public class ViewModelProperties
{
private ObservableCollection<ServerProperties> properties = new ObservableCollection<ServerProperties>();
public ObservableCollection<ServerProperties> Properties
{
get
{
return properties;
}
}
public void SetProperties()
{
properties.Clear();
for (var lineNumber = 0; lineNumber < MainWindow.lineCount; lineNumber++)
{
if (MainWindow.textProperties[lineNumber, 0] == null) break;
properties.Add(new ServerProperties(MainWindow.textProperties[lineNumber, 0],
MainWindow.textProperties[lineNumber, 1]));
}
}
}
Any time you wish to add new items to OC, just call the SetProperties method.

Categories

Resources