I've got a collection of ViewModels that are rendered as tabs using a style to pull out the relevant content to display on the tab:
public class TabViewModel : DependencyObject
{
public object Content
{
get { return (object)GetValue(ContentProperty); }
set
{
SetValue(ContentProperty, value);
}
}
}
Here's the TabControl:
<TabControl
ItemsSource={Binding MyCollectionOfTabViewModels}"
ItemContainerStyle="{StaticResource TabItemStyle}" />
And here's the style
<Style TargetType="TabItem" x:Key="TabItemStyle">
<Setter Property="Content" Value="{Binding Content}"/>
</Style>
We are creating an instance of a usercontrol and setting the "Content" property of the TabViewModel to that so that the usercontrol gets displayed in the TabItem's Content area.
MyCollectionOfViewModels.Add(new TabViewModel()
{
Content = new MyUserControl();
});
My question is, I would like to allow a MyUserControl (or any of its sub controls) added to the TabViewModel's Content property to be allowed to raise an event that the TabViewModel handles.
Anyone know how I would do that?
We've experimented using RoutedEvents and RoutedCommands, but haven't been able to get anything to work 100% and have it be compatible with MVVM. I really think that this could be done with a RoutedEvent or RoutedCommand, but I don't seem to be able to get this to work.
Note: I've removed some of the relevant Prism-specific code, but if you are wondering why we do something so silly, it is because we are trying to stay control agnostic by using Prism's RegionManager.
You could add a State property to your TabViewModel, and check the DependencyPropertyChanged events.
So imagine the following enum:
public enum TabViewModelState
{
True,
False,
FileNotFound
}
Then add a State property to your TabViewModel of this enum:
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(TabViewModelState), typeof(TabViewModel), new PropertyMetadata(OnStateChanged));
private static void OnStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
TabViewModel viewModel= (TabViewModel)obj;
//Do stuff with your viewModel
}
Use a two-way binding to this property in your control:
<CheckBox Checked="{Binding Path=State, Converter={StaticResource StateToBooleanConverter}, Mode=TwoWay}" />
And last but not least implement the converter that will convert to and from the original value needed for the control.. (in my example boolean <--> TabViewModelState):
public class StateToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
TabViewModelState state = (TabViewModelState) value;
return state == TabViewModelState.True;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool result = (bool) value;
return result ? TabViewModelState.True : TabViewModelState.False;
}
}
So now you have a State property that is managed by the UI, and throws changed events when you need to respond..
Hope this helps!
If you put a command on your ViewModel and just bind to that from your UserControl it will fire anyway. You don't have to bubble it, the UserControl will just find the command and use it.
If you had a delegate command 'GoCommand' on your ViewModel, your UserControls button binding would just look like this:
<Button Command="{Binding GoCommand}" >Go</Button>
I went through the same thought process thinking that the UserControl needs to bubble the command, but it dosn't - the binding will just hook itself up when it finds the command on the ViewModel.
Hope this helps and I havn't missed the point! ;)
You should have a look at Marlon Grech's Attached Command Behavior, which allows you to attach a ViewModel command to an GUI event.
Related
I'm trying to use Microsofts new SwipeControl to allows users to reveal an action that's available to the item. What I'm struggling with is removing the SwipeItems when the action is no longer available, I know it is possible as setting and nulling the LeftItems property on the SwipeControl works as I expect it to, but I need it to be more dynamic than this.
Some notes on setup, I'm using Caliburn Micro as the MVVM framework and its implementation of INotifyPropertyChanged. I'm not using x:Bind.
Attempt 1 - Control based on SwipeControl:
I tried creating a new control based on the SwipeControl. I added DepedencyProps for LeftItemsEnabled and InternalLeftItems. InternalLeftItems is used to store the list of Items between each removal and reset. To remove and reset the LeftItems property I use the LeftItemsEnabled property by changing the boolean. I set the LeftItems to my InternalLeftItems value or to null. See below for code:
Dependency Properties and PropertyChanged implementation
public static readonly DependencyProperty EnableLeftItemsProperty =
DependencyProperty.Register("EnableLeftItems", typeof(bool), typeof(UpdatedSwipeControl), new PropertyMetadata(false, LeftEnabledPropertyChanged));
public static readonly DependencyProperty InternalLeftItemsProperty =
DependencyProperty.Register("InternalLeftItems", typeof(SwipeItems), typeof(UpdatedSwipeControl), new PropertyMetadata(new SwipeItems()));
public SwipeItems InternalLeftItems {
get { return (SwipeItems)GetValue(InternalLeftItemsProperty); }
set { SetValue(InternalLeftItemsProperty, value); }
}
private static void LeftEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var control = d as UpdatedSwipeControl;
if (control == null) return;
control.EnableLeftItems = (bool)e.NewValue;
if ((bool)e.NewValue)
control.RightItems = control.InternalRightItems;
else
control.RightItems = null;
UpdateLayout();
}
Usage in View
<Page.Resources>
<SymbolIconSource x:Key="ButtonIconSource"
Symbol="Cancel" />
<SwipeItems x:Key="Items">
<SwipeItem Text="SWIPE ITEM"
IconSource="{StaticResource ButtonIconSource}"
Background="Red" />
</SwipeItems>
</Page.Resources>
<controls:UpdatedSwipeControl x:Name="swipeControl"
EnableLeftItems={Binding ItemsEnabled}
InternalLeftItems="{StaticResource Items}">
<TextBlock Text="SWIPE" />
</SwipeControl>
Property in ViewModel
private bool itemsEnabled;
public bool ItemsEnabled {
get {
return itemsEnabled;
}
set {
itemsEnabled = value;
NotifyOfPropertyChange(nameof(ItemsEnabled));
}
}
Problem with this attempt is the LeftEnabledPropertyChanged only fired once, when changing from the default value False to True on the initial bind. It doesn't fire again on any of the subsequent NotifyPropertyChanged's. I not sure if I've missed something in my setup of the DependencyProps or the PropertyChangedEvent.
Attempt 2 - Converter on LeftItems prop:
My second attempt was to use a Converter on the Binding of the boolean. I created a simple converter to return either SwipeItems in my resources or null. See below for code:
Converter code
public class ItemsConverter : IValueConverter {
public SwipeItems items { get; set; }
public object Convert(object value, Type targetType, object parameter, string language)
{
return (bool) value ? items : null;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
Implementation in view
<Page.Resources>
<sampleApp:ItemsConverter x:Key="converter" items="{StaticResource Items}"/>
<SwipeItems x:Key="Items">
<SwipeItem Text="SWIPE ITEM"
IconSource="{StaticResource ButtonIconSource}"
Background="Red" />
</SwipeItems>
</Page>
<SwipeControl x:Name="swipeControl"
LeftItems="{Binding ItemsEnabled, Converter={StaticResource converter}}">
<TextBlock Text="SWIPE" />
</SwipeControl>
Same implementation of the ItemsEnabled Property on the ViewModel.
The problem with this attempt was the converter was never hit and there were no Binding errors.
In summation I know it's possible to set/reset the SwipeItems property via code-behind which works ok in very simple scenarios. But anything more dynamic I can't get to work. Has anyone else run into this issue or have a workaround to this?
I'm making a Ribbon control for a WYSIWYG HTML editor. The ribbon has the typical Bold, Italic, Underline, FontFamily, etc. controls that you'd expect to see. I'll focus on the Bold functionality for this example.
I want the Ribbon to be reuseable, so I've added a Dependency Property (DP) and associated property wrapper to the control's code behind (standard boilerplate stuff):
public partial class EditorRibbon: UserControl
{
public static readonly DependencyProperty IsBoldProperty =
DependencyProperty.Register(
"IsBold",
typeof (bool),
typeof (EditorRibbon),
new PropertyMetadata(default(bool)));
public bool IsBold
{
get { return (bool) GetValue(IsBoldProperty); }
set { SetValue(IsBoldProperty, value); }
}
}
... and in the XAML I have my RibbonToggleButton, and I've bound the IsChecked property to the dependency property:
<UserControl x:Class="My.EditorRibbon">
<r:RibbonToggleButton Command="ToggleBold"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
SmallImageSource="{StaticResource ToggleBoldIcon}"
IsChecked="{Binding IsBold}" />
</UserControl>
In my Editor window, I've bound the IsBold property of the EditorRibbon to a conventional property on the window's ViewModel:
<Window x:class="My.MainWindow>
<My.EditorRibbon IsBold="{Binding SelectionIsBold}"/>
</Window>
Here is the SelectionIsBold property:
public bool SelectionIsBold
{
get { return _selection.IsBold(); }
}
... and I raise the NotifyPropertyChanged() event (in the MainWindow's ViewModel) whenever the selection in the RichTextBox changes:
public class MainWindowViewModel : BaseViewModel
{
public MainWindowViewModel(MainWindow window)
{
rtb.SelectionChanged += rtb_OnSelectionChanged;
}
private void rtb_OnSelectionChanged(object sender, RoutedEventArgs routedEventArgs)
{
NotifyPropertyChanged(()=>SelectionIsBold);
}
}
To my mind, this should be enough to change the IsChecked state of the RibbonToggleButton whenever the selection changes... but it doesn't. Despite changing the selection, and despite the NotifyPropertyChanged() firing as expected, a breakpoint on the SelectionIsBold property (yes, I've deselected VS's "Step Over Property" setting) is never hit. Somewhere, the request to refresh the value isn't propagating correctly.
Do I need to trigger NotifyPropertyChanged() on the IsBold property after the value is set in the setter?
Change the IsBold binding to the following
<UserControl x:Class="My.EditorRibbon" x:Name="EditorRibbonInstance">
<r:RibbonToggleButton Command="ToggleBold"
ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=Command.Text}"
SmallImageSource="{StaticResource ToggleBoldIcon}"
IsChecked="{Binding IsBold, ElementName=EditorRibbonInstance, Mode=TwoWay}" />
</UserControl>
With that you are sure that the binding is going to the property of the control and not to the datacontext of the control
You have to fire notifypropertychanged in ViewModel. Try somethings like this in ViewModel:
protected void FirePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
->> FirePropertyChanged("SelectionIsBold")
The reason is: now, your data context is ViewModel, all of binding to ViewModel must be triggered by ViewModel's properties
first of all, I never saw the injection of the Window to the ViewModel before... are you using some Kind of DI for the injection?
I think it is not a good idea to use the selection changed Event on viewmodel... This is not mvvm from my Point of view...
Are you updating the _selection somewhere? Might be that you always checking the same selection?!
You are not properly binding the command property of your button.
Should reflect something like this:
Command="{Binding ToggleBold}"
I am a beginner here in WPF and MVVM. I have certain controls on a window in my project. For example, I have a text box in my window. I am using MVVM Pattern and here I want to change the visible property of the text box from the view model.
One other thing is that, I want to change the visibility of the text box from the viewmodel based on some conditions.
Well, I googled it and google throws me some suggestions which were all different solutions and I'm in a total confusion.
Guess some one can help me figure this out.
I know this would be a piece of cake for the WPF MVVM Experts, but since I am trying to learn this stuff I require some code as examples.
Thanks
Since this is MVVM, you don't want to change the visibility of the textbox you actually want to disable some option.. Then - whether that option is enabled or disabled should reflect on the visibility of your Textbox.
So basically you want a Property in the ViewModel such as:
public bool CanMyPropertyBeChanged {get; set;}
Which you can change (of course you should probably implement INotifyPropertyChanged if you haven't already)...
And bind the visibility of the Textbox to this property, via a Converter:
<TextBox Visibility="{Binding CanMyPropertyBeChanged, Converter={StaticResource boolToVis}}" />
You can use the built-in BooleanToVisibilityConverter for this:
<BooleanToVisibilityConverter x:Key="boolToVis" />
In you XAML file add the following:
<Window.Resources>
<ResourceDictionary>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
</ResourceDictionary>
<Window.Resources>
On your textbox add:
<TextBox .... Visibility="{Binding IsVisibleBoolean, Converter={StaticResourcebooleanToVisibilityConverter}}" />
In your viewmodel add the IsVisibleBoolean property:
public bool IsVisibleBoolean
{
get; set;
}
you can do it multiple way
first of all you could bind it directly
XAML
<TextBox Visibility="{Binding myVisibility}"/>
VM Property
public Visibility myVisibility
{
get { return Visibility.Hidden; }
}
but you could also use a Converter (the recommended way)
XAML
xmlns:local="clr-namespace:yourNamespace">
<Window.Resources>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
</Window.Resources>
<TextBox Visibility="{Binding myVisibility,Converter={StaticResource BooleanToVisibility}}"/>
VM Property
public bool myVisibility
{
get { return false; }
}
BooleanToVisibilityConverter.cs
[ValueConversion(typeof(bool),typeof(Visibility))]
public sealed class BooleanToVisibilityConverter : IValueConverter
{
public bool IsReversed { get; set; }
public bool UseHidden { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var val = System.Convert.ToBoolean(value, CultureInfo.InvariantCulture);
if (this.IsReversed)
{
val = !val;
}
if (val)
{
return Visibility.Visible;
}
return this.UseHidden ? Visibility.Hidden : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a play button in a AudioRecord View.
Currently it is declered as:
<Button Width="72" Height="72" Style="{StaticResource RoundPlay}"
DataContext="{Binding ElementName=this, Path=DataContext}"
cmd:ButtonBaseExtensions.Command="{Binding PlayStopCommand}"
/>
When a user clicks the button, a PlayStopCommand in items ViewModel gets executed. I want the button to get its' style set to "RoundStop" whenever the sound is playing.
How can I bind the buttons' Style to a property in my ViewModel (what property type should I use), so that the look of the button is controllable from code?
I have RoundStop style defined, I just need a way to apply it to a button from code.
You should define the playing state in you viewmodel (Playing/Stopped), and bind Button.Style to that property using a converter. In your converter, return a different style (taken from App.Current.Resources) based on the current state.
Edit:
Here's an example of your converter should look like:
public class StateStyleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (PlaybackState)value == PlaybackState.Playing ? App.Current.Resources["RoundPlay"] : App.Current.Resources["RoundStop"];
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In this example, PlaybackState is an enum:
public enum PlaybackState
{
Playing,
Stopped
}
Then you should add the state property to your view model (The part where you notify the change depends on the framework you are using for MVVM):
private PlaybackState state;
public PlaybackState State
{
get { return state; }
set
{
state = value;
RaiseNotifyPropertyChanged("State");
}
}
Declare your converter in XAML:
<UserControl.Resources>
<converters:StateStyleConverter x:Key="StateStyleConverter"/>
</UserControl.Resources>
And finally bind it to the button:
<Button Width="72" Height="72" Style="{Binding State, Converter={StaticResource StateStyleConverter}}"
DataContext="{Binding ElementName=this, Path=DataContext}"
cmd:ButtonBaseExtensions.Command="{Binding PlayStopCommand}"
/>
You could use a ToggleButton and make the necessary visual changes in the visual states for checked/unchecked.
If you must do it the way your question states, then you can define the Style in the resources and then access it in the code-behind from this.Resources["YourStyleKey"]; Your problem will be getting it from the view to the view model, hence my first suggestion :)
I'm attempting to bind an IsLoading property to the Cursor property of my UI's LayoutRoot Grid. I'm trying to have the main app cursor become an hourglass whenever the property says it's loading.
I'm binding the property as follows:
<Grid Cursor="{Binding IsLoading, Converter={StaticResource CursorConverter}}">
The key "CursorConverter" maps to the BoolToCursorConverter in the resources. The converter code is:
public class BoolToCursorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter == null)
return ((bool)value == true) ? Cursors.Wait : Cursors.Arrow;
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
Cursor cursor = value as Cursor;
if (cursor != null)
return cursor == Cursors.Wait ? true : false;
return false;
}
}
When I try to run this though I get the XamlParseException "The given key was not present in the dictionary."
Any help would be appreciated, thanks,
Why you get that error
Do you have something like this in the content of a Resources property?
<local:BoolToCursorConverter x:Key="CursorConverter" />
If not then that's what is wrong but I'm guessing you already do.
In that case you I suspect you have placed it in the Resources property of the Grid it applies to. That would be why it can not be found. StaticResource are resolved immediately as the Xaml is parsed. Hence any key used must already be loaded into a resource dictionary prior to use. The Xaml parser knows nothing of the contents of the Grid's Resources property because it hasn't processed it yet. Hence:-
<UserControl>
<Grid Cursor="{Binding IsLoading, Converter={StaticResource CursorConverter}}">
<Grid.Resources>
<local:BoolToCursorConverter x:Key="CursorConverter" />
</Grid.Resources>
<!-- Contents here -->
</Grid>
</UserControl>
will fail. Where as:-
<UserControl>
<UserControl.Resources>
<local:BoolToCursorConverter x:Key="CursorConverter" />
</UserControl.Resources >
<Grid Cursor="{Binding IsLoading, Converter={StaticResource CursorConverter}}">
<!-- Contents here -->
</Grid>
</UserControl>
would at least not fail in finding the converter.
What you actually need to do
I've presented the above to answer your question but I can see it doesn't really help you. You can't bind to the Cursor property like this. (It doesn't expose a public identifier field, Xaml uses the NameOfThing + "Property" convention to find a field that is the DependencyProperty for the property being bound).
The solution is to create an Attached property:-
public class BoolCursorBinder
{
public static bool GetBindTarget(DependencyObject obj) { return (bool)obj.GetValue(BindTargetProperty); }
public static void SetBindTarget(DependencyObject obj, bool value) { obj.SetValue(BindTargetProperty, value); }
public static readonly DependencyProperty BindTargetProperty =
DependencyProperty.RegisterAttached("BindTarget", typeof(bool), typeof(BoolCursorBinder), new PropertyMetadata(false, OnBindTargetChanged));
private static void OnBindTargetChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = sender as FrameworkElement;
if (element != null)
{
element.Cursor = (bool)e.NewValue ? Cursors.Wait : Cursors.Arrow;
}
}
}
Now you can actually do the binding like this:-
<Grid local:BoolCursorBinder.BindTarget="{Binding IsLoading}">