WPF DependencyProperty chain notification - wpf

im exploring WPF world, i find a great example on the web about how to use binding on xml
http://www.codeproject.com/Articles/37854/How-to-Perform-WPF-Data-Binding-Using-LINQ-to-XML
Now im trying to extends this example: i want to create a "class in the middle" between the XElement and the UI and bind all togheder in a chain so, if i have a modification into the xml, then i have the property in the middle class updated then the UI updated too.
Here some code:
This is the class that wrap the XElement
public class XElementDataProvider : ObjectDataProvider
{
public XElementDataProvider()
{
ObjectInstance = XElement.Load(#"C:\MyFile.xml");
}
private static XElementDataProvider instance;
public static XElementDataProvider Instance
{
get
{
if (instance == null)
{
instance = new XElementDataProvider();
}
return instance;
}
}
}
This is the MiddleClass
public class MiddleClass : DependencyObject
{
XElementDataProvider xElementDataProvider;
XElement myxml;
public MiddleClass()
{
//here i get my dataprovider
xElementDataProvider = XElementDataProvider.Instance;
myxml = xElementDataProvider.Data as XElement;
//i bind my internal collection to the Elements...
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default//here i cant use TwoWay, give me //back an exeption
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
//just to have confirmation of the adding
myxml.Changed += new EventHandler<XObjectChangeEventArgs (myxml_Changed);
}
void myxml_Changed(object sender, XObjectChangeEventArgs e)
{
}
//i use a DependencyProperty to have also a change callback
public static readonly DependencyProperty XBookListProperty =
DependencyProperty.Register("XBookList", typeof(IEnumerable),
typeof(MiddleClass),
new PropertyMetadata(XBookPropertyChanged)
);
//here i have a notification only at start but no when i add a new book
private static void XBookPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MiddleClass middleClass = d as MiddleClass;
middleClass.XBookPropertyChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue);
}
private void XBookPropertyChanged(IEnumerable old, IEnumerable newValue)
{
}
//this is the propery i finally want to expose to the UI but im not able //to keep updated
public List<Book> bookList;
public List<Book> BookList
{
get
{
return bookList;
}
set
{
bookList = value;
}
}
//this is my internal list binded to the xml
private IEnumerable XBookList
{
get
{
return (IEnumerable)GetValue(XBookListProperty);
}
set
{
SetValue(XBookListProperty, value);
}
}
//here i try to add a book addind direcly to the xml//i expect a //notification of propery changed...but nothing
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
return true;
}
Book is a class thar repersents a book, let say it has only a name propery for now.
The UI class misses but it should bind on public List<Book> BookList and show books names to the user in a ListBox
Enyone knows why i dont recive any notification...or what i have to do to keep the public List<Book> BookList synchronized with private IEnumerable<XBookList>?

OK, after many attempts, the only solution I found is this one:
to have notifications when something changes in the IEnumerable<XBookList> you need to clear it ad rebind after you modify it.
In this way you have a first, not used notification, about the clear and then another notification about the new set.
Then in the handler you can synchronize the new list with the old one.
public bool AddBook(string name)
{
XElement newWorkSheet = new XElement("Book",
new XAttribute("Name", name)
);
myxml.Add(newWorkSheet);
ClearValue(XBookListProperty);
Binding binding = new Binding("Elements[book]")
{
Source = myxml,
Mode = BindingMode.Default
};
BindingOperations.SetBinding(this, XBookListProperty, binding);
return true;
}

Related

MVVM & business logic Layer

I have a problem with MVVM pattern and binding collection. My ViewModel provides a collection to the View but to get this collection I use this:
public BindingList<Car> BindingListCars { get; set; }
public CarsVm()
{
BindingListVoiture = carServices.ListCars;
}
When I bind my View on this List it's as if I bind directly my View on the Model because they use the same reference. So when I edit one property of a Car, the model is directly edited without using carServices validation method.
What is the best solution to correct this problem ?
Do I have to expose a copy of my Model to my View to not edit directly my Model from the View?
Do I have to use BindingList in my Model and subsribe to ListChanged in my carServices to validate each change?
You should either perform the validation directly in the Car class itself or expose wrapper objects instead of exposing the "real" Car objects to the view.
The following sample code should give you the idea about what I mean:
//the "pure" model class:
public class Car
{
public string Model { get; set; }
}
public class CarService
{
public List<CarWrapper> ListCar()
{
List<Car> cars = new List<Car>(); //get your Car objects...
return cars.Select(c => new CarWrapper(c, this)).ToList();
}
public bool Validate()
{
//
return true;
}
}
public class CarWrapper
{
private readonly Car _model;
CarService _service;
public CarWrapper(Car model, CarService service)
{
_model = model;
_service = service;
}
//create a wrapper property for each property of the Car model:
public string Model
{
get { return _model.Model; }
set
{
if(_service.Validate())
_model.Model = value;
}
}
}
Obviously if you expose an IEnumerable<Car> from your view model for the view to bind, you are effectively bypassing any validation that is dedined outside of the Car class if the view is able to set any properties of the Car class.
Thanks for your answer mm8,
With this solution I have to create one wrapper per class which need outside validation. It add work and during refactoring we have to edit the class and the Wrapper.
What do you think about this solution :
I put my list of vehicle in a binding list
My service subscribe to ListChanged event of this list
My service implement INotifyDataErrorInfo
For each modification in this list validation is executed
If there is an error ErrorsChanged event is raised
The view model subsribe to this event and retrieve error Data.
The view model subsribe to this event and retrieve error Data.
For example :
My services implementation :
public class VehicleServices : INotifyDataErrorInfo
{
private BindingList<Vehicle> _bindingListCar
public BindingList<Vehicle> BindingListCar
{
get return _bindingListCar;
}
private readonly Dictionary<string, ICollection<string>>
_validationErrors = new Dictionary<string, ICollection<string>>();
//INotifyDataErrorInfo implementation
public IEnumerable GetErrors(string propertyName)
public bool HasErrors
private void RaiseErrorsChanged(string propertyName)
public VehicleServices()
{
_bindingListCar = GetVehicles();
_bindingListCar.ListChanged += BindingListVehicleChanged;
}
private void BindingListVehicleChanged(object sender, ListChangedEventArgs e)
{
//Only modification is managed
if (e.ListChangedType != ListChangedType.ItemChanged) return;
switch(e.PropertyDescriptor.Name)
//Validate each property
//if there is ErrorsChanged is raised
}
}
And my ViewModel
public class CarVm : BindableBase
{
private ICollection<string> _errors;
public ICollection<string> Error
{
get
{
return _errors;
}
set
{
SetProperty(ref _errors, value);
}
}
private VehicleServices _carServices;
public BindingList<Vehicle> BindingListCar { get; set; }
public CarVm(VehicleServices carServices)
{
_carServices = carServices;
BindingListCar = new BindingList<Vehicle>(_carServices.BindingListCar);
_carServices.ErrorsChanged += _carServices_ErrorsChanged;
}
private void _carServices_ErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
Error = _carServices.ValidationErrors[e.PropertyName];
}
}
Do you think this is a good practice ?

Execute Method from Codehind using ViewModel WPF

I've abandoned the MVVM midway through app development just to get this app out.
I've written a method in the code behind to update the database/datagrid etc.
My application navigation is using Commands to the ViewModel firing some event but never touches the code-behind except one time to initialize the class.
So basically I push the button one time and it works with the default initial setting but I can't call my code-behind Update() method anymore once the view as been intialized.
How can I call this code-behind method from the view model?
Thanks!!
Update code
//Navigation ViewModel
//PaneVm.cs
public CommandExtension NewAssignmentCommand { get; set; }
private void CreateCommands()
{
NewAssignmentCommand = new CommandExtension(NewAssignment, CanNewAssignment);
}
GlobalCommands.NewAssignmentCommand = NewAssignmentCommand;
private bool CanNewGroupAssignment(object obj)
{
return true;
}
private void NewGroupAssignment(object obj)
{
OnPropertyChanged("NewGroupAssignmentCommand");
}
//MainVM.cs
// [Events]
void _PaneVm_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "NewGroupAssignmentCommand")
WorkspaceVm.CurrentVm = new NewAssignmentsVm();
}
//NewAssignmentVm.cs
//Constructor
public NewAssignmentsVm()
{
var rc = new RepositoryContext();
_RoResearchers = new ObservableCollection<Researcher>(rc.ResearcherData.GetAllResearchers());
_QuarterDateTime = DateTime.Now;
CreateCommands();
}
//NewAssignment.cs
//Code-behind
//The method
private void UpdateGrid()
{
report_datagrid.ItemsSource = null;
using (var rc = new RepositoryContext())
{
if (quarter_datepicker.SelectedDate != null)
{
if (!String.IsNullOrEmpty(reportType))
researchers = rc.ResearcherData.GetResearchersWeeksByQuarter(Convert.ToDateTime(quarter_datepicker.SelectedDate), reportType).ToList();
}
}
}
UPDATE 2:
I solved my problem based off this answer. I created a Global Action
public static class GlobalCommands
{
public static Action UpdateGrid { get; set; }
}
Then in my code-behind constructor I set the value public
MyCodeBehind()
{
GlobalCommands.UpdateGrid = new Action(() => this.UpdateGrid());
}
Didn't need to bind to the context again. Everything else was the same. Thank you
Main idea is:
class MyCodeBehind
{
public MyCodeBehind()
{
Action action = new Action(()=> this.SomeMethodIWantToCall());
var myVM = new MyVM(action); // This is your ViewModel
this.DataContext = myVM;
}
private void SomeMethodIWantToCall(){...}
}
class MyVM
{
private Action action;
public MyVM(Action someAction)
{
this.action = someAction;
}
private void SomeMethodInVM()
{
this.action(); // Calls the method SomeMethodIWantToCall() in your code behind
}
}
Instead of letting code-behind know about viewmodel, You can make use of NotifyOnSourceUpdated in your xaml binding.
Something like this:
<TextBlock Grid.Row="1" Grid.Column="1" Name="RentText"
Text="{Binding Path=Rent, Mode=OneWay, NotifyOnTargetUpdated=True}"
TargetUpdated="OnTargetUpdated"/>
Here, 'OnTargetUpdated' is a handler in your code behind. This handler will be invoked when "Rent" property of ViewModel is changed.
Details at MSDN

WPF - Can List<T> be used as a dependencyProperty

I have a simple search text box. This text box acts as a filter. I've now copied/pasted the code for the 5th time and enough is enough. Time for a custom control.
left and right brackets have been replaced with ()
My custom control will be simple. My problem is I want to have a dependencyProperty on this control that is of type List(T).
I created a test project to proof it out and make sure it works. It works well. Ignore List.
Below is the entire class. The problem is that the only thing holding me up is replacing List (Person) with List(T). Something like List where: T is Object
typeof(List(T) where: T is Object) <= Obviously I can't do that but gives an idea what I'm trying to accomplish.
public class SearchTextBox : TextBox
{
public static readonly DependencyProperty SourceProperty =
DependencyProperty.Register("FilterSource", typeof(List<Person>), typeof(SearchTextBox), new UIPropertyMetadata(null)); //I WANT THIS TO BE LIST<T>
public List<Person> FilterSource
{
get
{
return (List<Person>)GetValue(SourceProperty);
}
set
{
SetValue(SourceProperty, value);
}
}
public static readonly DependencyProperty FilterPropertyNameProperty =
DependencyProperty.Register("FilterPropertyName", typeof(String), typeof(SearchTextBox), new UIPropertyMetadata());
public String FilterPropertyName
{
get
{
return (String)GetValue(FilterPropertyNameProperty);
}
set
{
SetValue(FilterPropertyNameProperty, value);
}
}
public SearchTextBox()
{
this.KeyUp += new System.Windows.Input.KeyEventHandler(SearchBox_KeyUp);
}
void SearchBox_KeyUp(object sender, System.Windows.Input.KeyEventArgs e)
{
ICollectionView view = CollectionViewSource.GetDefaultView(FilterSource);
view.Filter = null;
view.Filter = new Predicate<object>(FilterTheSource);
}
bool FilterTheSource(object obj)
{
if (obj == null) return false;
Type t = obj.GetType();
PropertyInfo pi = t.GetProperty(FilterPropertyName);
//object o = obj.GetType().GetProperty(FilterPropertyName);
String propertyValue = obj.GetType().GetProperty(FilterPropertyName).GetValue(obj, null).ToString().ToLower();
if (propertyValue.Contains(this.Text.ToLower()))
{
return true;
}
return false;
}
}
No; that's not possible.
Instead, just use the non-generic typeof(IList).

Runtime datacontext with Castle Windsor

I'm working on adding a Windsor IoC container to an existing WinForms application that uses an MVP UI design pattern. I'm trying to determine a good approach to resgistering a datacontext that depends on a connection string supplied at runtime. The problem is that I cannot create a datacontext until the user selects a database, i.e. a 'connection string' after the application has loaded. Granted only one datacontext is generally used, but sometimes a user need to switch to a different database, i.e. creating a differnet datacontext. This leads to additional runtime dependencies as well.
public interface IProductsView
{
event EventHandler<ProductSelectedEventArgs> ProductSelectedEvent;
event EventHandler<StringEventArgs> ProductStatusEvent;
void ClearProductList();
void DisplayProductList(IList<Product> products);
Control Control { get; }
IProductsPresenter Presenter { get; set; }
}
public class ProductsPresenter : IProductsPresenter
{
public IProductsView View { get; set; }
private IProductRepository Repository { get; set; }
public ProductsPresenter(IProductsView view, IProductRepository repository)
{
View = view;
View.Presenter = this;
Repository = repository;
}
public void ProductSelected(IList<Product> products)
{
throw new NotImplementedException();
}
public void ShowProductList(string name)
{
IList<Product> productList;
if (string.IsNullOrEmpty(name))
productList = Repository.GetProducts();
else
productList = Repository.GetProductsByName(name);
View.DisplayProductList(productList);
}
}
public class ProductDao : IDisposable, IProductRepository
{
private MeasurementDataContext dataContext;
public ProductDao(MeasurementDataContext context)
{
dataContext = context;
}
public List<Product> GetProducts()
{
return dataContext.Products.Select(p => Mapper.Map(p)).ToList().OrderBy(x => x.Name).ToList();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
if (dataContext != null)
{
dataContext.Dispose();
dataContext = null;
}
}
~ProductDao()
{
this.Dispose(false);
}
}
So this means that the Presenter in my View is null until the IProductRepository is created, which in turn depends on creating a MeasurementDataContext. I have these component regisitered in a IWindsorInstaller like so:
container.Register(Component.For<IProductsView>()
.ImplementedBy<ViewProductsControl>());
container.Register(Component.For<IProductsPresenter>()
.ImplementedBy<ProductsPresenter>());
Do I need to use Named and DependsOn which supply a unique name and connectionString argument for each datacontext?
What I currently do to register the data context at runtime after the user has selected a database
kernel.Register(Component.For<MeasurementDataContext>()
.UsingFactoryMethod(() => new MeasurementDataContext(conn)));
and then `Resolve' my Views and set their Presenters. I know this is not good design, but it's a brute force way of resolving my dependcies.
Thanks
UPDATE:
I changed the way I registered my datacontext's in the installer to the following:
container.Register(Component.For<DataContext>().ImplementedBy<MeasurementDataContext>().Named("Localhost").DependsOn(new { connectionString = conn }));
and then modified my model's constructor to:
public ProductDao(DataContext context)
{
dataContext = context as MeasurementDataContext;
}
All components will resolve with the right key:
kernel.Resolve<DataContext>(cbo.SelectedItem.ToString());
What about injecting a wrapper class to hold the connection string and have the datacontext objects use that? Something along these lines:
public class ConnectionStringProvider : IConnectionStringProvider
{
private string _value;
public event EventHandler ConnectionStringChanged;
public string ConnectionString
{
get { return _value; }
set
{
_value = value;
var del = ValueChanged;
if (del != null)
del(this, EventArgs.Empty);
}
}
}
Register this with and singleton lifestyle. This way your application can set or update the connection string on a single object and everyone who depends on it will be notified of the change.

How to Notify That View Should Get New Value of Calculated Field

I am working on a WP7 app that displays some times on one page. I have a code behind that has an ObservableCollection of objects. Each object has a calculated property that uses DateTime.Now to determine the time that's displayed on the page. I can't figure out how to "notify" that the property has changed since the property doesn't change, the current time is changing (just once per second). Any ideas? Here's the jist of what I've got:
//my business object
public class Widget
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private DateTime? _start;
public DateTime? Start
{
get { return _start; }
set { _start = value; }
}
public TimeSpan? TimeSinceStart
{
get { return Start.HasValue ? DateTime.Now - Start.Value : default(TimeSpan); }
}
}
//my viewmodel
public class WidgetDisplayerViewModel : BaseViewModel
{
public WidgetDisplayerViewModel()
{
TimeUpdateTimer.Tick += new EventHandler(TimeUpdateTimer_Tick);
TimeUpdateTimer.Interval = new TimeSpan(0, 0, 1);
TimeUpdateTimer.Start();
}
public WidgetDisplayerViewModel(string selectedCategory) : this()
{
Category = MockDataService.GetCategory(selectedCategory);
Category.Widgets = MockDataService.GetWidgets(selectedCategory).ToObservableCollection();
}
public DispatcherTimer TimeUpdateTimer = new DispatcherTimer();
private DateTime _currentTime;
public DateTime CurrentTime
{
get { return _currentTime; }
set {
_currentTime = value;
NotifyPropertyChanged("CurrentTime");
}
}
public Category Category { get; set; }
void TimeUpdateTimer_Tick(object sender, EventArgs e)
{
CurrentTime = DateTime.Now;
}
}
And then the view is very simple and just needs to display the CurrentTime and then for each Widget in the collection it needs to show the TimeSinceStart. The CurrentTime is getting updated each second by the timer and that gets propogated to the view. That one is easy because the timer is setting it and so I have a chance to call NotifyPropertyChanged("CurrentTime"), but how would I "notify" that all of the TimeSinceStart getters should be called to update the calculated value for each Widget since I'm not setting them?
Thanks!
You'll have to manually refresh the property one way or another. I see you already have a timer ticking every second. So I can suggest you two solutions:
1/ Define a "UpdateTime" method in the Widget object. In this method, call NotifyPropertyChanged("TimeSinceStart"). When the timer is ticking, enumerate the list of widgets, and call the UpdateTime method on each.
2/ Create a global object implementing the INotifyPropertyChanged interface, and holding the value of CurrentTime. Make each of your Widget objects subscribe to the PropertyChanged event of this global class to know when the time is updated. Then, when the event is triggered, call NotifyPropertyChanged("TimeSinceStart").
This can be a tricky one to work out and it can get very messy very fast.
I would suggest you stick with your current approach of having only one timer which is initialised in the main viewmodel. You then have to ask yourself the question - does the age (TimeSinceStart) of the Widget belong on the Widget, or is it purely for display/informational purposes? Is it a core piece of information that each Widget must keep during its lifespan?
This looks to me like it is for display purposes only. So my suggestion is this: once you have called GetWidgets, you could enumerate through each Widget and wrap it in a thin viewmodel of its own. The constructor for that viewmodel takes two parameters - the timer from the main viewmodel, and the Widget. You then subscribe to the timer's Tick event, and from that you notify that the TimeSinceStart property has changed.
public class WidgetWrapper : INotifyPropertyChanged
{
public WidgetWrapper(DispatcherTimer timer, Widget widget)
{
_widget = widget;
timer.Tick += TimerTick;
}
private void TimerTick(object sender, EventArgs e)
{
OnPropertyChanged("TimeSinceStart");
}
public Widget Widget { get { return _widget; } }
public TimeSpan? TimeSinceStart
{
get { return _widget.Start.HasValue ? DateTime.Now - _widget.Start.Value : default(TimeSpan); }
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
private readonly Widget _widget;
}
public class WidgetDisplayerViewModel : BaseViewModel
{
public WidgetDisplayerViewModel(string selectedCategory) : this()
{
Category = MockDataService.GetCategory(selectedCategory);
var wrappedWidgets = new ObservableCollection<WidgetWrapper>();
MockDataService.GetWidgets(selectedCategory).ForEach(widget => wrappedWidgets.Add(new WidgetWrapper(TimeUpdateTimer, widget)));
Category.Widgets = wrappedWidgets;
}
}
Wrapping a DTO (entity, Data Transfer Object) with its own viewmodel is a quite common approach when adding functionality to an entity. If you use this appoach you will have to slightly modify any UI bindings that were targetting properties on the Widget, as those UI elements will now be dealing with a WidgetWrapper (or you can just surface the required properties in the WidgetWrapper itself, then no bindings have to change).
Invoke the NotifyPropertyChanged method for the specified property.
public DateTime CurrentTime
{
get { return _currentTime; }
set {
_currentTime = value;
NotifyPropertyChanged("CurrentTime");
NotifyPropertyChanged("TimeSinceStart");
}
}
Subscribe all widgets to CurrentTime PropertyChanged event in Widget constructor
private Widget()
{
App.ViewModel.PropertyChanged += (s, e) =>
{
if (e.PropertyName.Equals("CurrentTime")
{
NotifyPropertyChanged("TimeSinceStart");
}
};
}

Resources