How do I add a custom convention to Caliburn.Micro for the IsEnabled property of controls - something like having NameEnabled bound to IsEnabled in parallel to Name bound to Text on a TextBox.
In a way, what I want to achieve is similar to the way that a CanSave property can be used to enable/disable a button bound to a Save method, but generic for all controls.
Caliburn.Micro right now (1.3.1) doesn't really support this "multiple" conventions for the same FrameworkElement, what you have described.
EDIT:
However you can hook into the ViewModelBinder.BindProperties method and there you can implement your own extra convetion.
I went one step further and implemented a prototype which works, but it's not robust, nor elegant and probably not the correct way to do this. But it can be a starting point:
static AppBootstrapper()
{
ConventionManager.AddElementConvention<FrameworkElement>(
UIElement.IsEnabledProperty,
"IsEnabled",
"IsEnabledChanged");
var baseBindProperties = ViewModelBinder.BindProperties;
ViewModelBinder.BindProperties =
(frameWorkElements, viewModels) =>
{
foreach (var frameworkElement in frameWorkElements)
{
var propertyName = frameworkElement.Name + "Enabled";
var property = viewModels
.GetPropertyCaseInsensitive(propertyName);
if (property != null)
{
var convention = ConventionManager
.GetElementConvention(typeof(FrameworkElement));
ConventionManager.SetBindingWithoutBindingOverwrite(
viewModels,
propertyName,
property,
frameworkElement,
convention,
convention.GetBindableProperty(frameworkElement));
}
}
return baseBindProperties(frameWorkElements, viewModels);
};
}
You can enable/disable a control by setting a boolean property in your ViewModel and you just bind to IsEnabled in XAML:
TextBox Name="SerialNumber" IsEnabled="{Binding IsReadOnly}"...
ViewModel:
private bool isReadOnly;
public bool IsReadOnly
{
get { return isReadOnly; }
set
{
this.isReadOnly = value;
NotifyOfPropertyChange( () => IsReadOnly);
}
}
Related
What's the appropriate way to handle running an async operation when an item is selected from a two-way bound control such as a combobox (wpf data binding)?
When I have a two-way binding property (e.g. SelectedValue on ComboBox) I don't think I can use Stephen Cleary's NotifyTaskCompletion because when a user selects a value from the dropdown, the ComboBox itself would need to modify the bound Result property, which is the Task's result.
The only viable solution I've come up with is calling an async Task -method from the databound setter without awaiting the result. This should be fine as long as the async-method triggers a property changed event for whatever ui-related stuff is being done, and that any exceptions are picked up and propagated to the ui accordingly, right?
I assume this would be a common case in async WPF applications. How do you guys approach this?
My solution so far:
<ComboBox
ItemsSource="{Binding PossibleItems}"
DisplayMemberPath="Name"
SelectedValue="{Binding SelectedItem}"/>
...
public Item SelectedItem
{
get { return m_selectedItem; }
set
{
m_selectedItem = value;
OnPropertyChanged();
InitializeAsyncAndFirePropertyChanged(); // async Task method not awaited - gives compiler warning CS4014
}
}
public async Task InitializeAsyncAndFirePropertyChanged(ObservableCollection<RFEnvironment> possibleRfEnvironments)
{
//should check this method for exceptions and propagate them to the UI via databinding
OtherDataBoundProperty = await GetSomeStringFromWebAsync();
}
public string OtherDataBoundProperty
{
get { return m_otherDataBoundProperty; }
set
{
m_otherDataBoundProperty = value;
OnPropertyChanged();
}
}
Note: I have found similar questions asked, but none that addresses two-way bindings on controls such as a Combobox.
If you are using a functional reactive MVVM framework such as ReactiveUI, you would simply observe the SelectedItem property and kick off any operation you want when the property is set. e.g.:
this.WhenAnyValue(x => x.SelectedItem)
.Subscribe(async _ => await InitializeAsyncAndFirePropertyChanged());
A property itself should ne be kicking off background operations, but a view model may do this when a property is set.
Please refer to the docs for more information: https://reactiveui.net/docs/concepts/.
I have a similar issue with async call in property setter when using WCF databinding.
My solution is slightly better, because in your case when an exception occurs in InitializeAsyncAndFirePropertyChanged, no exception is thrown nor caught. The modified code is below. It uses task continuation to throw an exception and raising OnPropertyChanged. OnPropertyChanged call can stay in an original place, it depends on your needs.
public class MyViewModel: INotifyPropertyChanged
{
private readonly TaskScheduler _uiScheduler;
public MyViewModel()
{
_uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
}
public Item SelectedItem
{
get { return m_selectedItem; }
set
{
m_selectedItem = value;
InitializeAsyncAndFirePropertyChanged()
.ContinueWith(t =>
{
if (t.Exception != null)
{
throw t.Exception;
}
OnPropertyChanged();
}, _uiScheduler);
}
}
public async Task InitializeAsyncAndFirePropertyChanged(ObservableCollection<RFEnvironment> possibleRfEnvironments)
{
//should check this method for exceptions and propagate them to the UI via databinding
OtherDataBoundProperty = await GetSomeStringFromWebAsync();
}
public string OtherDataBoundProperty
{
get { return m_otherDataBoundProperty; }
set
{
m_otherDataBoundProperty = value;
OnPropertyChanged();
}
}
.... other code to support INotifyPropertyChanged
}
WinForms NumericUpDown allows us to select a range of the text inside it using the Select(Int32, Int32) method. Is there any way to set/retrieve the starting point of text selected, the number of characters selected and the selected part of text like we can do that for other textbox-like controls using the SelectionStart, SelectionLength and SelectedText properties?
The NumericUpDown control has an internal TextBox accessible from the controls collection. It's the second control in the collection following the UpDownButtons control. Since WinForms isn't under development any longer, it's practically safe to say the underlying architecture of the NumericUpDown control isn't going to change.
By inheriting from the NumericUpDown control, you can easily expose those TextBox properties:
public class NumBox : NumericUpDown {
private TextBox textBox;
public NumBox() {
textBox = this.Controls[1] as TextBox;
}
public int SelectionStart {
get { return textBox.SelectionStart; }
set { textBox.SelectionStart = value; }
}
public int SelectionLength {
get { return textBox.SelectionLength; }
set { textBox.SelectionLength = value; }
}
public string SelectedText {
get { return textBox.SelectedText; }
set { textBox.SelectedText = value; }
}
}
Similar to LarsTech's answer, you can quickly cast the NumericUpDown.Controls[1] as a TextBox to access these properties without creating a new class.
((TextBox)numericUpDown1.Controls[1]).SelectionLength; // int
((TextBox)numericUpDown1.Controls[1]).SelectionStart; // int
((TextBox)numericUpDown1.Controls[1]).SelectedText; // string
This is not possible with the vanilla NumericUpDown control.
In this article, the author explains how he subclassed the NumericUpDown control to expose the underlying TextBox object, and thus exposing the "missing" properties:
http://www.codeproject.com/Articles/30899/Extended-NumericUpDown-Control
He uses reflection to get a reference to the underlying TextBox object:
private static TextBox GetPrivateField(NumericUpDownEx ctrl)
{
// find internal TextBox
System.Reflection.FieldInfo textFieldInfo = typeof(NumericUpDown).GetField("upDownEdit", System.Reflection.BindingFlags.FlattenHierarchy | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
// take some caution... they could change field name
// in the future!
if (textFieldInfo == null) {
return null;
} else {
return textFieldInfo.GetValue(ctrl) as TextBox;
}
}
Cheers
I've been exploring the Caliburn Micro MVVM Framework just to get a feel for it, but I've run into a bit of a problem. I have a TextBox bound to a string property on my ViewModel and I would like the property to be updated when the TextBox loses focus.
Normally I would achieve this by setting the UpdateSourceTrigger to LostFocus on the binding, but I don't see any way to do this within Caliburn, as it has setup the property binding for me automatically. Currently the property is updated every time the content of the TextBox changes.
My code is very simple, for instance here is my VM:
public class ShellViewModel : PropertyChangeBase
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyOfPropertyChange(() => Name);
}
}
}
And inside my view I have a simple TextBox.
<TextBox x:Name="Name" />
How to I change it so the Name property is only updated when the TextBox loses focus, instead of each time the property changes?
Just set the binding explictly for that instance of the TextBox and Caliburn.Micro won't touch it:
<TextBox Text="{Binding Name, UpdateSourceTrigger=LostFocus}" />
Alternatively, if you want to change the default behaviour for all instances of TextBox, then you can change the implementation of ConventionManager.ApplyUpdateSourceTrigger in your bootstrapper's Configure method.
Something like:
protected override void Configure()
{
ConventionManager.ApplyUpdateSourceTrigger = (bindableProperty, element, binding) =>{
#if SILVERLIGHT
ApplySilverlightTriggers(
element,
bindableProperty,
x => x.GetBindingExpression(bindableProperty),
info,
binding
);
#else
if (element is TextBox)
{
return;
}
binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
#endif
};
}
Is it possible to bind data in the "wrong" direction? I want a value in a custom control to be bound to my ViewModel. I've tried binding with mode "OneWayToSource" but I can't get it to work.
Scenario (simplified):
I have a custom control (MyCustomControl) that has a dependency property that is a list of strings:
public class MyCustomControl : Control
{
static MyCustomControl()
{
//Make sure the template in Themes/Generic.xaml is used.
DefaultStyleKeyProperty.OverrideMetadata(typeof (MyCustomControl), new FrameworkPropertyMetadata(typeof (MyCustomControl)));
//Create/Register the dependency properties.
CheckedItemsProperty = DependencyProperty.Register("MyStringList", typeof (List<string>), typeof (MyCustomControl), new FrameworkPropertyMetadata(new List<string>()));
}
public List<string> MyStringList
{
get
{
return (List<string>)GetValue(MyCustomControl.MyStringListProperty);
}
set
{
var oldValue = (List<string>)GetValue(MyCustomControl.MyStringListProperty);
var newValue = value;
SetValue(MyCustomControl.MyStringListProperty, newValue);
OnPropertyChanged(new DependencyPropertyChangedEventArgs(MyCustomControl.MyStringListProperty, oldValue, newValue));
}
}
public static readonly DependencyProperty MyStringListProperty;
}
The control also contains code to manipulate this list.
I use this custom control in a UserControl that has a ViewModel. The ViewModel has a property that is also a list of strings:
public List<string> MyStringsInTheViewModel
{
get
{
return _myStringsInTheViewModel;
}
set
{
if (value != _myStringsInTheViewModel)
{
_myStringsInTheViewModel = value;
OnPropertyChanged("MyStringsInTheViewModel");
}
}
}
private List<string> _myStringsInTheViewModel;
Now I want to bind the list in my custom control (MyStringList) to the list in my ViewModel (MyStringsInTheViewModel) so that when the list is changed in the custom control it is also changed in the ViewModel. I've tried this but can't get it to work...
<myns:MyCustomControl MyStringList="{Binding Path=MyStringsInTheViewModel, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}">
How can I make such a binding?
Use ObservableCollection<T> instead of List<T>. It implements INotifyCollectionChanged Interface.
I have an INotifyProperty Screen item that I have bound to a wpf control.
Ok... I Simplified everything and am posting more code. I have a MainViewModel with the selected screen property.
public Screen SelectedScreen
{
get { return this.selectedScreen; }
set
{
this.selectedScreen = value;
this.OnPropertyChanged("SelectedScreen");
}
}
I have a textbox that is bound to this property:
<TextBlock Text="{Binding Path=SelectedScreen.ScreenNumber}" />
This all works initially. I have created another control that is changing the selected screen with the following code.
public Screen SelectedScreen
{
get { return (Screen)GetValue(SelectedScreenProperty); }
set
{
this.SetValue(SelectedScreenProperty, value);
for (int x = 0; x < this.Screens.Count; ++x)
this.Screens[x].IsSelected = false;
value.IsSelected = true;
}
}
public ObservableCollection<Screen> Screens
{
get { return (ObservableCollection<Screen>)GetValue(ScreensProperty); }
set { this.SetValue(ScreensProperty, value); }
}
public static readonly DependencyProperty SelectedScreenProperty =
DependencyProperty.Register("SelectedScreen",
typeof(Screen),
typeof(ScreenSelection));
public static readonly DependencyProperty ScreensProperty =
DependencyProperty.Register("Screens",
typeof(ObservableCollection<Screen>),
typeof(ScreenSelection),
new UIPropertyMetadata(new ObservableCollection<Screen>()));
This screen selection control is working. When I change screens and put a breakpoint on the set property of SelectedScreen it is called which then calls the SelectedScreen property of the MainViewModel. So the event is firing, but the textbox isn't updated even though it binds correctly the first time.
Does the class which contains the SelectedScreen property implement INotifyPropertyChanged? When the SelectedScreen property changes, the containing class should raise the PropertyChanged event, and typically, WPF should update the Binding.
Thank you gehho for looking at this. I figured it out and there is no way you had enough information to be able too. I was inheriting from ViewModelBase in the MainViewModel that was inheriting from ObservableObject where I implemented INotifyPropertyChanged. The problem is that I implemented the methods for INotifyPropertyChanged in both classes and WPF was listening to the wrong one. Very obscure. Very annoying. Very lasjkdf;ashdoh