If for example I have a view model class like
class ViewModel {
Data Data { get; set;}
}
and
class Data : IClonable {
public int Value0 {get; private set;}
public int Value1 {get; private set;}
Data SetValue0(int value){
var r = (Data) this.Clone();
r.Value0 = value;
return r;
}
Data SetValue1(int value){
var r = (Data) this.Clone();
r.Value1 = value;
return r;
}
}
in my XAML, with an instance of ViewModel as the DataContext, I would like to two way bind my text boxes like so
<TextBox Text="{Binding Data.Value0}"/>
<TextBox Text="{Binding Data.Value1}"/>
now obviously this doesn't work. What I need to explain to the binding is that to set the subproperties I have to call the method Set${propName} and then replace the entire property from the root of the path.
Note the actual implementation I have of the immutable object pattern is much more complex than above but a solution for the above pattern I could extrapolate for my more complex setup. Is there anything I can provide to the binding to make it perform what I want?
For information my actual immutable object pattern allows things like
var newValue = oldValue.Set(p=>p.Prop1.Prop2.Prop3.Prop4, "xxx");
I now have in my XAML code
<c:EditForLength Grid.Column="2" Value="{rx:ImmutableBinding Data.Peak}"/>
This becomes ImmutableBinding is a markup extension that returns a binding to a proxy object so you could imagine that the above is rewritten as
<c:EditForLength Grid.Column="2" Value="{Binding Value, ValidatesOnNotifyDataErrors=True}"/>
with the binding direct to the proxy object and the proxy object shadowing the real data and the validation errors. My code for the proxy and immutable binding object is
Note the code uses ReactiveUI calls and some of my own custom code but the general pattern of building a custom binding using a proxy to mediate validation errors should be clear. Also the code is probably leaking memory and I'll be checking that soon.
[MarkupExtensionReturnType(typeof(object))]
public class ImmutableBinding : MarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
public ImmutableBinding(PropertyPath path) { Path = path; }
/// <summary>
/// Returns a custom binding that inserts a proxy object between
/// the view model and the binding that maps immutable persistent
/// writes to the DTO.
/// </summary>
/// <param name="provider"></param>
/// <returns></returns>
override public object ProvideValue( IServiceProvider provider )
{
var pvt = provider as IProvideValueTarget;
if ( pvt == null )
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if ( frameworkElement == null )
{
return this;
}
if ( frameworkElement.DataContext == null )
{
return "";
}
var proxy = new Proxy();
var binding = new Binding()
{
Source = proxy,
Path = new PropertyPath("Value"),
Mode = BindingMode.TwoWay,
ValidatesOnDataErrors = true
};
var path = Path.Path.Split('.');
var head = path.First();
var tail = path.Skip(1)
.ToList();
var data = frameworkElement.DataContext as ValidatingReactiveObject;
if (data == null)
return null;
data.ErrorsChanged += (s, e) =>
{
if ( data.Errors.ContainsKey(Path.Path) )
{
proxy.Errors["Value"] = data.Errors[Path.Path];
}
else
{
proxy.Errors.Clear();
}
proxy.RaiseValueErrorChanged();
};
var subscription = data
.WhenAnyDynamic(path, change => change.Value )
.Subscribe(value => proxy.Value = value);
proxy
.WhenAnyValue(p => p.Value)
.Skip(1)
.DistinctUntilChanged()
.Subscribe
(value =>
{
var old = data.GetType()
.GetProperty(head)
.GetValue(data) as Immutable;
if (old == null) throw new NullReferenceException("old");
var #new = old.Set(tail, value);
data.GetType()
.GetProperty(head)
.SetValue(data, #new);
});
binding.ValidatesOnNotifyDataErrors = true;
return binding.ProvideValue(provider);
}
}
and the proxy object. Note that ValidatingReactiveObject implements INotifyDataErrorInfo
public class Proxy : ValidatingReactiveObject<Proxy>
{
object _Value;
public object Value
{
get { return _Value; }
set { this.ValidateRaiseAndSetIfChanged(ref _Value, value); }
}
public Proxy() { Value = 0.0; }
public void RaiseValueErrorChanged()
{
RaiseErrorChanged("Value");
RaiseErrorChanged("Error");
OnPropertyChanged("Error");
}
}
Related
I have a ComboBox (DropDownList style) in a Windows Form which has a data-source set to a BindingList, and has the SelectedValue property bound to a viewmodel's property.
Note that the binding is set to OnPropertyChanged rather than OnValidate, this is because when using OnValidate the control will not necessarily update the ViewModel if the form is closed or loses focus (but the control still thinks it has focus. On the Compact Framework there is no way to 'force validation' so I have to use OnPropertyChanged.
There's a problem which is reproducible on both desktop Windows Forms and Smart Device Windows Forms: when attempting to select or set the current item in the combobox (using the mouse or keyboard) the value will not "stick" until it is set twice - that is, you need to select the same item twice before the combobox's value will change.
There are no exceptions thrown (even caught exceptions) and no diagnostics reports to speak of.
I don't think this is a bug in the framework, and it's interesting how it happens on both Desktop and Compact Framework.
Here's my code:
Form1.cs
public partial class Form1 : Form {
private ViewModel _vm;
public Form1() {
InitializeComponent();
this.bindingSource1.Add( _vm = new ViewModel() );
}
}
Form1.Designer.cs (relevant lines)
//
// bindingSource1
//
this.bindingSource1.DataSource = typeof( WinForms.Shared.ViewModel );
//
// comboBox1
//
this.comboBox1.DataBindings.Add( new System.Windows.Forms.Binding( "SelectedValue", this.bindingSource1, "SelectedSomeTypeId", true, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged ) );
this.comboBox1.DataSource = this.someTypeListBindingSource;
this.comboBox1.DisplayMember = "DisplayText";
this.comboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.comboBox1.FormattingEnabled = true;
this.comboBox1.Location = new System.Drawing.Point( 12, 27 );
this.comboBox1.Name = "comboBox1";
this.comboBox1.Size = new System.Drawing.Size( 182, 21 );
this.comboBox1.TabIndex = 0;
this.comboBox1.ValueMember = "Id";
//
// someTypeListBindingSource
//
this.someTypeListBindingSource.DataMember = "SomeTypeList";
this.someTypeListBindingSource.DataSource = this.bindingSource1;
ViewModel.cs
public class ViewModel : INotifyPropertyChanged {
public ViewModel() {
this.SomeTypeList = new BindingList<SomeType>();
for(int i=0;i<5;i++) {
this.SomeTypeList.Add( new SomeType() {
Id = i + 1,
Name = "Foo" + ((Char)( 'a' + i )).ToString()
} );
}
this.SelectedSomeTypeId = 2;
}
public BindingList<SomeType> SomeTypeList { get; private set; }
private Int64 _selectedSomeTypeId;
public Int64 SelectedSomeTypeId {
get { return _selectedSomeTypeId; }
set {
if( _selectedSomeTypeId != value ) {
_selectedSomeTypeId = value;
OnPropertyChanged("SelectedSomeTypeId");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(String propertyName) {
PropertyChangedEventHandler handler = this.PropertyChanged;
if( handler != null ) handler( this, new PropertyChangedEventArgs(propertyName) );
}
}
public class SomeType {
public String Name { get; set; }
public Int64 Id { get; set; }
public String DisplayText {
get { return String.Format("{0} - {1}", this.Id, this.Name ); }
}
}
I have never found the 'right' way around this issue and generally use one of two ways to make things work:
Direct: Just bypass the binding mechanism for this one entry
combo1.SelectedIndexChanged += (s,e) _viewModel.Item = combo1.SelectedItem;
Generic Binding: Make a custom ComboBox and override the OnSelectedIndexChanged event to force the binding update.
public class BoundComboBox : ComboBox
{
protected override void OnSelectedIndexChanged(EventArgs e)
{
var binding = this.DataBindings["SelectedItem"];
if( binding != null )
binding.WriteValue();
base.OnSelectedIndexChanged(e);
}
}
I have been reading Mark Seeman's book on dependency injection in .NET and I'm struggling to configure composition root in WPF application.
My container will be registered in the application startup method:
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var container = new Container();
container.Configure(r =>
{
r.For<IAccountServices>().Use<AccountServicesProxy>();
r.For<MainWindow>().Use<MainWindow>();
});
}
This makes sense as the application startup represents my composition root.
WPF windows in my application are based on view models. View models use constructor injection. E.g. I may compose a view model by injecting implementation of IAccountServices.
When it comes to creating my main window, I can do the following inside of the OnStartup method:
var mainWindow = container.GetInstance<MainWindow>();
mainWindow.Show();
Once I'm inside of the main window, I might want open up another window. So far I've been able to come up with one way of doing this, which is to create a window factory and ask window factory to resolve instance of the window. I'll have to make sure that window factory is available in every view model that might need to open a new window. In my mind this is as bad as passing IoC container around my application (service locator anti-pattern comes to mind).
Does this approach seem right to you? My gut feeling tells me that this is wrong, but I haven't come up with a better way of achieving this (yet).
I think before implement patterns of behavior, such as a Mediator, and the like, need to decide on a generic pattern for easy application structure. For this purpose, namely, for the create independent windows, well suited Abstract factory pattern.
Creation of the windows can be implemented on the side ViewModel using methods such as IDialogService. But I think that this task should be implemented on the side View, because the Window object refers to the View and not to ViewModel. So, you must create MVVM style architecture that it allows create independent windows using design patterns.
I created a project in which an Abstract factory creates a Window on the side of the View using the attached behavior. Abstract factory also implements the Singleton pattern to create a global point of access and to ensure the uniqueness of the newly constructed object. Attached behavior implicitly implements pattern Decorator who is a wrapper for an abstract factory that is used on the side of XAML. To an Abstract factory does not refer to objects which are located in ViewModel is used a Proxy pattern which is a ContentControl with DataTemplate without DataType. Also used Command pattern for independent action between objects. As a result, this project uses the following patterns:
Abstract factory
Singleton
Decorator
Proxy
Command
The project structure looks like this:
In the attached behavior has attached dependency property Name, which is transmitted in the name of the new window. For him registered PropertyChangedEvent, which is a call Make method an abstract factory:
private static void IsFactoryStart(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var window = sender as Window;
if (window == null)
{
return;
}
if (e.NewValue is String && String.IsNullOrEmpty((string)e.NewValue) == false)
{
_typeWindow = (string)e.NewValue;
if (_typeWindow != null)
{
var newWindow = WindowFactory.Instance.Make(_typeWindow);
newWindow.Show();
}
}
}
WindowFactory together with the Singleton pattern looks like this:
public class WindowFactory : IWindowFactory
{
#region WindowFactory Singleton Instance
private static WindowFactory _instance = null;
private static readonly object padlock = new object();
public static WindowFactory Instance
{
get
{
lock (padlock)
{
if (_instance == null)
{
_instance = new WindowFactory();
}
return _instance;
}
}
}
#endregion
public Window Make(string TypeWindow)
{
if (TypeWindow.Equals("WindowOneViewProxy"))
{
var windowOne = new Window();
windowOne.Width = 450;
windowOne.Height = 250;
windowOne.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowOne.Title = TypeWindow;
windowOne.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowOne;
}
else if (TypeWindow.Equals("WindowTwoViewProxy"))
{
var windowTwo = new Window();
windowTwo.Width = 500;
windowTwo.Height = 200;
windowTwo.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowTwo.Title = TypeWindow;
windowTwo.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowTwo;
}
else if (TypeWindow.Equals("WindowThreeViewProxy"))
{
var windowThree = new Window();
windowThree.Width = 400;
windowThree.Height = 140;
windowThree.WindowStartupLocation = WindowStartupLocation.CenterScreen;
windowThree.Title = TypeWindow;
windowThree.ContentTemplate = Application.Current.Resources[TypeWindow] as DataTemplate;
return windowThree;
}
else
throw new Exception("Factory can not create a: {0}" + TypeWindow);
}
}
For the property Window.ContentTemplate set DataTemplate from resources. ContentTemplate is responsible for the visual representation, in order to bind properties from ViewModel, you need to set the object to Content. But in this case, the Abstract factory reference will to ViewModel, and to avoid them and using the proxy pattern as follows:
WindowOneProxyView
<DataTemplate x:Key="WindowOneViewProxy">
<ContentControl ContentTemplate="{StaticResource WindowOneViewRealObject}">
<ViewModels:WindowOneViewModel />
</ContentControl>
</DataTemplate>
WindowOneViewRealObject
<DataTemplate x:Key="WindowOneViewRealObject" DataType="{x:Type ViewModels:WindowOneViewModel}">
<Grid>
<Label Content="{Binding Path=WindowOneModel.TextContent}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Background="Beige" />
<Button Content="One command"
Width="100"
Height="30"
HorizontalAlignment="Center"
Command="{Binding OneCommand}" />
</Grid>
</DataTemplate>
In DataTemplate proxy is not specified DataType, but it is in the real object.
In MainViewModel has commands to simply set the window name, which will give input for attached behavior:
MainModel
public class MainModel : NotificationObject
{
#region TypeName
private string _typeName = null;
public string TypeName
{
get
{
return _typeName;
}
set
{
_typeName = value;
NotifyPropertyChanged("TypeName");
}
}
#endregion
}
MainViewModel
public class MainViewModel
{
#region MainModel
private MainModel _mainModel = null;
public MainModel MainModel
{
get
{
return _mainModel;
}
set
{
_mainModel = value;
}
}
#endregion
#region ShowWindowOneCommand
private ICommand _showWindowOneCommand = null;
public ICommand ShowWindowOneCommand
{
get
{
if (_showWindowOneCommand == null)
{
_showWindowOneCommand = new RelayCommand(param => this.ShowWindowOne(), null);
}
return _showWindowOneCommand;
}
}
private void ShowWindowOne()
{
MainModel.TypeName = "WindowOneViewProxy";
}
#endregion
#region ShowWindowTwoCommand
private ICommand _showWindowTwoCommand = null;
public ICommand ShowWindowTwoCommand
{
get
{
if (_showWindowTwoCommand == null)
{
_showWindowTwoCommand = new RelayCommand(param => this.ShowWindowTwo(), null);
}
return _showWindowTwoCommand;
}
}
private void ShowWindowTwo()
{
MainModel.TypeName = "WindowTwoViewProxy";
}
#endregion
#region ShowWindowThreeCommand
private ICommand _showWindowThreeCommand = null;
public ICommand ShowWindowThreeCommand
{
get
{
if (_showWindowThreeCommand == null)
{
_showWindowThreeCommand = new RelayCommand(param => this.ShowWindowThree(), null);
}
return _showWindowThreeCommand;
}
}
private void ShowWindowThree()
{
MainModel.TypeName = "WindowThreeViewProxy";
}
#endregion
public MainViewModel()
{
MainModel = new MainModel();
}
}
MainWindow looks as:
<Window x:Class="WindowFactoryNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WindowFactoryNamespace.ViewModels"
xmlns:AttachedBehaviors="clr-namespace:WindowFactoryNamespace.AttachedBehaviors"
AttachedBehaviors:WindowFactoryBehavior.Name="{Binding Path=MainModel.TypeName}"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="300" Width="300">
<Window.DataContext>
<this:MainViewModel />
</Window.DataContext>
<WrapPanel>
<Button Content="WindowOne"
Margin="10"
Command="{Binding ShowWindowOneCommand}" />
<Button Content="WindowTwo"
Margin="10"
Command="{Binding ShowWindowTwoCommand}" />
<Button Content="WindowThree"
Margin="10"
Command="{Binding ShowWindowThreeCommand}" />
</WrapPanel>
</Window>
Test View-ViewModel for the first window looks like this (they practically identical):
WindowOneModel
public class WindowOneModel : NotificationObject
{
#region TextContent
private string _textContent = "Text content for WindowOneView";
public string TextContent
{
get
{
return _textContent;
}
set
{
_textContent = value;
NotifyPropertyChanged("TextContent");
}
}
#endregion
}
WindowOneViewModel
public class WindowOneViewModel
{
#region WindowOneModel
private WindowOneModel _windowOneModel = null;
public WindowOneModel WindowOneModel
{
get
{
return _windowOneModel;
}
set
{
_windowOneModel = value;
}
}
#endregion
#region OneCommand
private ICommand _oneCommand = null;
public ICommand OneCommand
{
get
{
if (_oneCommand == null)
{
_oneCommand = new RelayCommand(param => this.One(), null);
}
return _oneCommand;
}
}
private void One()
{
WindowOneModel.TextContent = "Command One change TextContent";
}
#endregion
public WindowOneViewModel()
{
WindowOneModel = new WindowOneModel();
}
}
This project is available at this link.
Output
MainWindow
WindowOne
WindowTwo
WindowThree
IMHO, there is no need to over complicate the solution for the sake of MVVM purity. You risk the subsequent developers not understanding your elegant solution and break it. In fact there is a good chance of that as "pure" implementations tend to be not that readable because of the complexity.
IMHO, any solution where a problem is permanently solved under an abstraction with minimal code overhead and simplicity in its usage is better than doing considerable overhead every time the solution is used even if "purity" is achieved(it won't serve any purpose). The problem of showing dialog in the application has to be solved once and it should be easy to use it in the future.
Composing view models is perfectly fine, and could make life easier by allowing view models to interact without drama
A dialog service can be created which will act as a wrapper for all your dialog needs in the application. You can inject the Dialog Service and the child view models which needs to be displayed in a window, to your parent view model. When you need to display the window, ask the Dialog service to do it, passing it the view model instance and view name.
Note:code is not complied or tested
public class DialogService : IDialogService
{
IEventAggregator _eventAggregator;
bool _fatalError;
//Provides a wrapper function which will connect your view and view model and open a
//dialog
public Window ShowCustomDialog<TViewModel>(string name, TViewModel viewModel, bool
modal, double left, double top, Action<bool?> OnClose, int width, int height)
{
if (_fatalError == true)
{
return null;
}
Window view = new Window(name);
if (viewModel != null)
{
view.DataContext = viewModel;
}
if (left != -1.0 && top != -1.0)
{
view.WindowStartupLocation = WindowStartupLocation.Manual;
view.Left = left;
view.Top = top;
}
else
{
view.WindowStartupLocation = WindowStartupLocation.CenterScreen;
}
if (width != -1 && height != -1)
{
view.Width = width;
view.Height = height;
}
view.Closed += (o, e) =>
{
_eventAggregator.GetEvent<NotifyDialogAction>().Publish(false);
if (OnClose != null)
{
OnClose(e.DialogResult);
}
};
view.Loaded += (o, e) =>
{
_eventAggregator.GetEvent<NotifyDialogAction>().Publish(true);
Window window = o as Window;
if (window != null)
{
double dialogWidth = window.ActualWidth;
double screenWidth =
Application.Current.RootVisual.RenderSize.Width;
double dialogLeft = window.Left;
if (dialogLeft + dialogWidth > screenWidth)
{
window.Left = screenWidth - dialogWidth;
}
double dialogHeight = window.ActualHeight;
double screenHeight =
Application.Current.RootVisual.RenderSize.Height;
double dialogTop = window.Top;
if (dialogTop + dialogHeight > screenHeight)
{
window.Top = screenHeight - dialogHeight;
}
}
};
if (modal)
{
view.ShowDialog();
}
else
{
view.Show();
}
return view;
}
//Add more functions. For example to pop up a message box etc.
}
Usage
public class ComposedVM
{
public ViewModelA objA{get;set;}
public ViewModelB objB{get;set;}
IDialogService dialogService{get;set;}
public ComposedVM(ViewModelA a, ViewModelB b, IDialogService dlg )
{
objA = a;
objB = b;
dialogService = dlg
}
public void OnShowWindowACommand()
{
dialogService .ShowCustomDialog<object>(
DialogNames.ViewA/*view name constant*/, objA, true, -1.0, -1.0,
result =>
{
if (result == true)
{
dialogService.ShowMessageDialog(ApplicationStrings.SuccessFulOperation);
}
});
}
}
An event/message based communication can be used between modules. Using it for related view models in a module is an overkill IMHO.
Pushing container instance through constructor is a bad idea in 99% of cases, because container is a service locator. The main disadvantages of this approach are:
dependency from concrete implementation of container;
unclear API of your classes, which also leads to fragile unit tests.
There are many ways to create window in MVVM fashion:
using Mediators (like IMessenger in MvvmLight, IEventAggregator in Caliburn.Micro);
using special IDialogService;
using attached behaviours;
using Action that inserted via ViewModel constructor;
using Controllers.
I've just discovered that WPF Markup extension instances are reused in control templates. So each copy of the control template gets the same set of markup extensions.
This doesn't work if you want the extension to maintain some state per control it is attached to. Any idea how to solve this.
Don't store state in the Markup extension. Store it another way. For example.
public abstract class DynamicMarkupExtension : MarkupExtension
{
public class State
{
public object TargetObject { get; set; }
public object TargetProperty { get; set; }
public void UpdateValue(object value)
{
if (TargetObject != null)
{
if (TargetProperty is DependencyProperty)
{
DependencyObject obj = TargetObject as DependencyObject;
DependencyProperty prop = TargetProperty as DependencyProperty;
Action updateAction = () => obj.SetValue(prop, value);
// Check whether the target object can be accessed from the
// current thread, and use Dispatcher.Invoke if it can't
if (obj.CheckAccess())
updateAction();
else
obj.Dispatcher.Invoke(updateAction);
}
else // TargetProperty is PropertyInfo
{
PropertyInfo prop = TargetProperty as PropertyInfo;
prop.SetValue(TargetObject, value, null);
}
}
}
}
public sealed override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
State state = new State();
if (target != null)
{
state.TargetObject = target.TargetObject;
state.TargetProperty = target.TargetProperty;
return ProvideValueInternal(serviceProvider, state);
}
else
{
return null;
}
}
protected abstract object ProvideValueInternal(IServiceProvider serviceProvider, State state);
}
is a base class for handling the type of problem where you need to update the property the markup
extension is attached to at run time. For example a markup extension for binding to ISubject as
<TextBox Text="{Markup:Subscription Path=Excenter, ErrorsPath=Errors}"/>
using the SubscriptionExtension as below. I had had trouble with the code when I used it
within templates but I fixed it so the MarkupExtension did not store state in itself
using ReactiveUI.Ext;
using ReactiveUI.Subjects;
using ReactiveUI.Utils;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ReactiveUI.Markup
{
[MarkupExtensionReturnType(typeof(BindingExpression))]
public class SubscriptionExtension : DynamicMarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
[ConstructorArgument("errorsPath")]
public PropertyPath ErrorsPath { get; set; }
public SubscriptionExtension() { }
Maybe<Exception> currentErrorState = Maybe.None<Exception>();
public SubscriptionExtension(PropertyPath path, PropertyPath errorsPath)
{
Path = path;
ErrorsPath = errorsPath;
}
class Proxy : ReactiveObject, IDataErrorInfo, IDisposable
{
string _Value;
public string Value
{
get { return _Value; }
set { this.RaiseAndSetIfChanged(value); }
}
public string Error
{
get { return currentError.Select(e => e.Message).Else(""); }
}
public string this[string columnName]
{
get { return currentError.Select(e => e.Message).Else(""); }
}
public IObservable<Maybe<Exception>> Errors { get; set; }
public Maybe<Exception> currentError = Maybe.None<Exception>();
private CompositeDisposable Subscriptions = new CompositeDisposable();
public Proxy(IObservable<Maybe<Exception>> errors)
{
Errors = errors;
var subscription = errors.Subscribe(e => currentError = e);
Subscriptions.Add(subscription);
}
public void Dispose()
{
Subscriptions.Dispose();
}
}
protected override object ProvideValueInternal(IServiceProvider serviceProvider, DynamicMarkupExtension.State state )
{
var pvt = serviceProvider as IProvideValueTarget;
if (pvt == null)
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if (frameworkElement == null)
{
return this;
}
DependencyPropertyChangedEventHandler myd = delegate(object sender, DependencyPropertyChangedEventArgs e){
state.UpdateValue(MakeBinding(serviceProvider, frameworkElement));
};
frameworkElement.DataContextChanged += myd;
return MakeBinding(serviceProvider, frameworkElement);
}
private object MakeBinding(IServiceProvider serviceProvider, FrameworkElement frameworkElement)
{
var dataContext = frameworkElement.DataContext;
if (dataContext is String)
{
return dataContext;
}
ISubject<string> subject = Lens.Empty<string>().Subject;
IObservable<Maybe<Exception>> errors = Observable.Empty<Maybe<Exception>>();
Binding binding;
Proxy proxy = new Proxy(errors);
bool madeit = false;
if (dataContext != null)
{
subject = GetProperty<ISubject<string>>(dataContext, Path);
if (subject != null)
{
errors = GetProperty<IObservable<Maybe<Exception>>>
(dataContext
, ErrorsPath) ?? Observable.Empty<Maybe<Exception>>();
proxy = new Proxy(errors);
}
madeit = true;
}
if(!madeit)
{
subject = new BehaviorSubject<string>("Binding Error");
}
// Bind the subject to the property via a helper ( in private library )
var subscription = subject.TwoWayBindTo(proxy, x => x.Value);
// Make sure we don't leak subscriptions
frameworkElement.Unloaded += (e, v) => subscription.Dispose();
binding = new Binding()
{
Source = proxy,
Path = new System.Windows.PropertyPath("Value"),
ValidatesOnDataErrors = true
};
return binding.ProvideValue(serviceProvider);
}
private static T GetProperty<T>(object context, PropertyPath propPath)
where T : class
{
if (propPath==null)
{
return null;
}
try
{
object propValue = propPath.Path
.Split('.')
.Aggregate(context, (value, name)
=> value.GetType()
.GetProperty(name)
.GetValue(value, null));
return propValue as T;
}
catch (NullReferenceException e)
{
throw new MemberAccessException(propPath.Path + " is not available on " + context.GetType(),e);
}
}
}
}
I've got a problem using System.Windows.Interactivity.Interaction attached behavior class (from Expression Blend SDK 4). I'd like to define a pair of triggers for System.Windows.Window class in XAML Style element. But as the TriggersProperty field of System.Windows.Interactivity.Interaction class is private and there is no SetTriggers method in this class, I've got an error 'Set property System.Windows.Setter.Property threw an exception. -> Value cannot be null. Parameter name: property' when running the following code.
I really want to use the triggers and actions in styles, because I'd like to use them for my window-descendant control. Of course I can use my custom behavior or directly code my window-descendant class with triggers-analogue logic, but I'd like to use already existent triggers and actions of the expression library and my own, not declining them, simply because the TriggersProperty of Interaction class is hidden and I can't set it through style.
Is any workaround for the problem? With Reflection or someway other?
PS. I already tried to declare custom static class with TriggersProperty attached dependency property, registered with the help of AddOwner method, but no help - at the end it still tries to access the same TriggersProperty in the same System.Windows.Interactivity.Interaction class.
<Window
x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:windowsInteractivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<Window.Style>
<Style TargetType="Window">
<Setter Property="Title" Value="WindowStyleTest"/>
<Setter Property="windowsInteractivity:Interaction.Triggers">
<Setter.Value>
<windowsInteractivity:EventTrigger EventName="MouseDown"/>
</Setter.Value>
</Setter>
</Style>
</Window.Style>
</Window>
!!!Update!!!
Okay I took it a bit further. I extended the extension to perform all the work including setting the Triggers collection.
TriggerCollectionExtension
The Extension That does all the heavy lifting.
Note: The first time ProvideValue is called it will be from loading the style so the TargetValue is a Setter.
[ContentProperty("Triggers")]
public class TriggerCollectionExtension : MarkupExtension
{
public string EventName { get; set; }
public string CommandName { get; set; }
public object CommandParameter { get; set; }
public System.Windows.Interactivity.TriggerCollection Triggers { get; private set;}
public TriggerCollectionExtension()
{
var trigCollectionType =
typeof(System.Windows.Interactivity.TriggerCollection);
var triggers = (System.Windows.Interactivity.TriggerCollection)
trigCollectionType.GetConstructor(
BindingFlags. NonPublic | BindingFlags. Instance,
null, Type.EmptyTypes, null).Invoke (null);
// Cheat to get around this problem.
// must have IsFrozen set to false to modify
var methCreateCore = trigCollectionType.GetMethod("CreateInstanceCore",
BindingFlags.NonPublic | BindingFlags.Instance);
var cloneTriggers =
(System.Windows.Interactivity.TriggerCollection)
methCreateCore.Invoke(triggers, null);
this.Triggers = cloneTriggers;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var target = serviceProvider.GetService(typeof(IProvideValueTarget)) as
IProvideValueTarget;
// The first time this is called is when loading the style.
// At that point the TargetObject is of type Setter.
// Return this (The MarkupExtension) and it will be reevaluated when the style
// is applied.
var hostcontrol = target.TargetObject as Control;
if (hostcontrol != null)
{
var cloneTriggers = this.Triggers;
var eventTrigger = new EventTrigger(this.EventName);
var trigbase = eventTrigger as TriggerBase;
trigbase.Attach(hostcontrol);
var commandAction = new CommandAction(hostcontrol, this.CommandName,
this.CommandParameter);
eventTrigger.Actions.Add(commandAction);
cloneTriggers.Add(eventTrigger);
Interaction.SetShadowTriggers(hostcontrol, this.Triggers);
return null;
}
else
{
return this;
}
return null;
}
}
Interaction
The re-ownership/exposure of the TriggersCollection.
<!-- language: c# -->
/// <summary>
/// Helps workaround the bug in the deployed interaction DLL.
/// The DependencyProperty is registered as ShadowTriggers and the Setter Getter is
/// SetTriggers() GetTriggers().
/// The result is compile error for XAML if anything but Shadowtriggers is used and
/// runtime error.
/// </summary>
public static class Interaction
{
static Interaction()
{
var interActionType = typeof(System.Windows.Interactivity.Interaction);
var triggersProperty = (DependencyProperty)interActionType.InvokeMember(
"TriggersProperty",
BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.GetField,
null, null, null);
ShadowTriggersProperty = triggersProperty.AddOwner(typeof(Interaction));
}
public static readonly DependencyProperty ShadowTriggersProperty;
public static System.Windows.Interactivity.TriggerCollection
GetShadowTriggers(DependencyObject d)
{
return
(System.Windows.Interactivity.TriggerCollection)
d.GetValue(ShadowTriggersProperty);
}
public static void
SetShadowTriggers(
DependencyObject d,
System.Windows.Interactivity.TriggerCollection value)
{
d.SetValue(ShadowTriggersProperty, value);
}
}
CommandAction
A custom TriggerAction that looks up the Command on the DataContext.
<!-- language: c# -->
public class CommandAction : TriggerAction<FrameworkElement>
{
FrameworkElement control;
private string commandName;
object commandParameter;
private ICommand actualCommand;
public CommandAction(FrameworkElement control, string commandName,
object commandParameter)
{
this.control = control;
this.commandName = commandName;
this.commandParameter = commandParameter;
object datacontext;
if (this.FindDataContext(this.control, out datacontext))
{
var datacontextType = datacontext.GetType();
var propCommand = datacontextType.GetProperty(this.commandName);
this.actualCommand = propCommand.GetValue(datacontext, null) as ICommand;
}
}
private bool FindDataContext(FrameworkElement control, out object datacontext)
{
datacontext = default(object);
var parent = VisualTreeHelper.GetParent(control);
while (parent != null)
{
var parentFrame = parent as FrameworkElement;
if (parentFrame != null)
{
datacontext = parentFrame.DataContext;
if (datacontext != null)
{
return true;
}
}
var parentFrameContent = parent as FrameworkContentElement;
if (parentFrameContent != null)
{
datacontext = parentFrameContent.DataContext;
if (datacontext != null)
{
return true;
}
}
parent = VisualTreeHelper.GetParent(parent);
}
return false;
}
protected override void Invoke(object parameter)
{
if (this.actualCommand != null)
{
if (this.actualCommand.CanExecute(parameter))
{
this.actualCommand.Execute(parameter);
}
}
}
}
Wow long time reader first time posting code. I finally learned why the code doesn't always cut and paste so well. It took so many tries to submit this update.
I am sure there are reasons like disk space, parsing, or rendering speed, and the editor maintains state on failure to submit excellently.
I got it, why the error appears. That's because at runtime it's searching an Attached Dependency property by string name, that is "ShadowTriggers" (as it specified in System.Windows.Interactivity assembly, Interaction static constructor). So I created the own custom static class and inherit the Triggers Dependency Property from System.Windows.Interaction there (via Reflection and AddOwner, just exposed the property as ShadowTriggersProperty). It worked! But... Now I have to provide a TriggerCollection instance to the Style's Property Value Setter, and the constructor of the class is internal. Suppose it is a no way further.
I have a entity class in a C# library class and linked to Silverlight class library
(entities must be in C# class because of legacy compatiblity with other systems)
Example (C# library):
public class TestClass
{
private string _testValue;
public string TestValue
{
get { return _testValue; }
set
{
if (_testValue!= value)
{
_testValue = value;
OnPropertyChanged("TestValue");
}
}
}}
This class is linked to Silverlight class library.
On a MVVM there is a property
private TestClass _testProp = new TestClass();
public TestClass TestProp
{
get
{
return _testProp ;
}
set
{
if (value != _testProp )
{
_testProp = value;
RaisePropertyChanged("TestProp");
PressCommand.CanExecuteChanged();
}
}
}
The property is binded to a control in XAML
<TextBox Text="{Binding TestProp.TestValue, Mode=TwoWay}">
<Button Content="Press" Command="{Binding PressCommand}" />
I want to control the button with RelayCommands CanExecute depended on the TestValue in TestClass...
PressCommand = new RelayCommand(() =>
{
DoSomething();
}, () => TestProp.TestValue != string.empty);
However, if the TestValue in changed (different then empty string), PressCommand CanExecute doen't seem to notice the change and is not enabled, making it unusable...
Is it possible to use the CanExecute with this kind of set-tu
What you need to do is call PressCommand.CanExecuteChanged() when the value changes. To do this nicely listen for the property change of the value in the property
VM
public TestClass TestProp
{
get
{
return _testProp ;
}
set
{
if (value != _testProp )
{
if(_testProp != null)
{
_testProp.PropertyChanged -= TestPropChanged;
}
_testProp = value;
if(_testProp != null)
{
_testProp.PropertyChanged += TestPropChanged;
}
RaisePropertyChanged("TestProp");
PressCommand.CanExecuteChanged();
}
}
}
private void TestPropChanged(object sender, PropertyChangedEventArgs e)
{
//Can check for specific property if required...
PressCommand.CanExecuteChanged();
}