Button's IsEnabled property depends on Command property? - wpf

I am surprised by the following behavior of Button.
I have created a usercontrol for button + image.
<UserControl x:Name="root">
<Button Command="{Binding ClickCommand, ElementName=root}" Click="Button_Click">
<StackPanel>
<Image Source="{Binding ImageSource, ElementName=root, Mode=OneWay}"
Stretch="None" />
<TextBlock Padding="4,0"
Text="{Binding Caption, ElementName=root, Mode=OneWay}" />
</StackPanel>
</Button>
</UserControl>
The ClickEvent and ClickCommandProperty are in code-behind as RoutedEvent and DependencyProperty.
It works as expected, but I do not understand why the UserControl cannot be enabled when I do not set the ClickCommand in xaml at least to an empty string.
<c:ImageButton Caption="Refresh" Click="refresh_Click" x:Name=btnRefresh
ClickCommand="" ImageSource="{StaticResource refresh}" />
So without ClickCommand="" the code btnRefresh.IsEnabled = true; has no effect.
Also: the IsEnabledChanged event is invoked by .IsEnabled = true; and not again to set it to false? Mysteries.

Related

Getting current Object in Control template

<ControlTemplate TargetType="{x:Type ListBoxItem}">
<StackPanel>
<StackPanel Margin="0,0,28,0" Orientation="Horizontal" Visibility="{Binding IsEditable,Converter={StaticResource BooleanToVisibilityConverter}}">
<TextBlock Foreground="Gray" Text="{Binding DateCreated,Converter={StaticResource DateTimeConverter}}" FontFamily="/Assets/Fonts/Berthold Akzidenz Grotesk BE Regular.ttf" FontSize="16"/>
<TextBlock Text=":" Foreground="Gray"/>
<TextBlock Width="20"/>
<TextBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" BorderThickness="0" Name="TrainerNoteText" Text="{Binding TrainerNote}" FontFamily="/Assets/Fonts/Berthold Akzidenz Grotesk BE Regular.ttf" Foreground="Black" FontSize="16" TextWrapping="Wrap" KeyUp="EditTrainerNote" Width="400"/>
</StackPanel>
</StackPanel>
</ControlTemplate>
The above control template is in a listview. The textbox inside is editable. So when user presses the enter key, I need to get the current object associated with that. How to do this?
You can listen to KeyDown RoutedEvent at ListView level.
http://msdn.microsoft.com/en-us/library/system.windows.input.keyboard.keydown.aspx
Its an attached event and its handler can be placed anywhere in VisualTree.
Here is an example:
<StackPanel TextBox.KeyDown="OnKeyDownHandler">
<TextBox Width="300" Height="20"/>
</StackPanel>
And this is the handler:
public void OnKeyDownHandler(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
TextBox tbx = (TextBox)sender;
tbx.....
}
}
You know, you really should define what your items look like in a DataTemplate defined in the ListBox.ItemTemplate property and not the ListBoxItem.Template property. Based on the example from the linked page:
<ListBox Width="400" Margin="10" ItemsSource="{Binding YourCollectionProperty}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=TaskName}" />
<TextBlock Text="{Binding Path=Description}"/>
<TextBlock Text="{Binding Path=Priority}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When Binding a collection property to the ListBox.Items property, all of the UI elements inside the DataTemplate will have access to the properties of the type that is in the collection. In this example, the type that populates the YourCollectionProperty collection has TaskName, Description and Priority properties in it. You can replace these properties with those from the type that is in your collection property.
If you set up your properties to implement the INotifyPropertyChanged interface (or use DependencyProperties then any updates in the UI elements will automatically be updated in the data objects in the view model/code behind. Therefore, there is no need to add KeyDown or KeyUp handlers. For more information, please read the Data Binding Overview page on MSDN.

RelativeSource and Popup

The problem is that RelativeSource does not work in the following case. I use silverlight 5.
//From MainPage.xaml
<Grid x:Name="LayoutRoot" Background="White" Height="100" Width="200">
<Popup IsOpen="True">
<TextBlock Text="{Binding Path=DataContext, RelativeSource={RelativeSource AncestorType=Grid}}" />
</Popup>
</Grid>
//From MainPage.xaml.cs
public MainPage()
{
InitializeComponent();
DataContext = "ololo";
}
If I set a breakpoint on the binding, I'll get Error:
System.Exception: BindingExpression_CannotFindAncestor.
If I use ElementName=LayoutRoot instead of RelativeSource, everything will be OK.
Why does the relative source binding not work?
Popup is like ContextMenu , ToolTip controls , They are not added to the VisualTree. For this you will have to do like
<Grid x:Name="LayoutRoot" Height="100" Width="200" Background="Black">
<Popup Grid.Row="0" x:Name="popup" DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Mode=Self}}">
<TextBlock Text="{Binding DataContext, ElementName=popup}" Background="Red" Width="30" Height="30" />
</Popup>
</Grid>
public MainWindow()
{
InitializeComponent();
DataContext = "abcd";
popup.PlacementTarget = LayoutRoot;
}
I hope this will help.Not like in case of ContextMenu or Tooltip , here you will also have to specify the PlacementTarget.
You can make small hack: setup DataContext via resources.
<Grid.Resources>
<Style TargetType="TextBlock">
<Setter Property="DataContext" Value="{Binding ElementName=myGrid, Path=DataContext}" />
</Style>
</Grid.Resources>
As others have mentioned, it's because the Popup is not part of the visual tree. Instead, you can use the Popup's PlacementTarget property to get back to the visual tree:
<Grid x:Name="LayoutRoot" Background="White" Height="100" Width="200">
<Popup IsOpen="True">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Popup}},
Path=PlacementTarget.DataContext}" />
</Popup>
</Grid>
Popups are not part of the visual tree.
Relative Source "Gets or sets the binding source by specifying its location relative to the position of the binding target (MSDN)". Since Popups are not part of the visual tree of the control that is showing it, it will not be able to resolve anything outside of the popup.

Change binded object property on button click in WPF

I have binded an object to a WPF control. How can I toggle the object property "IsEditMode" on click of the edit button using only xaml and no code behind? Here is sample code of xaml -
<Label Style="{StaticResource TitleLabel}"
Content="{Binding Path=GroupTitle}"
Visibility="{Binding Path=IsEditMode, Converter={StaticResource boolToVis}}"
HorizontalAlignment="Left" />
<Button Content="Edit" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<!--Toggle the bindedobject.IsEditMode property of click of button-->
</EventTrigger>
</Button.Triggers>
</Button>
using only xaml and no code behind
I don't think it's possible with no C# (or VB) code at all, but you can do it with no code-behind, using the MVVM pattern. So you will have C# code, just not in the code-behind...
If you go this way, you need to expose a command from your ViewModel:
private DelegateCommand _enterEditModeCommand;
public ICommand EnterEditModeCommand
{
get
{
if (_enterEditModeCommand== null)
{
_enterEditModeCommand= new DelegateCommand(EnterEditMode);
}
return _enterEditModeCommand;
}
}
private void EnterEditMode()
{
IsEditMode = true;
}
And bind your button to that command:
<Button Content="Edit" Command="{Binding EnterEditModeCommand}"
HorizontalAlignment="Right" VerticalAlignment="Center">
There's a control for that already in the framework:
<Label Style="{StaticResource TitleLabel}"
Content="{Binding Path=GroupTitle}"
Visibility="{Binding Path=IsEditMode, Converter={StaticResource boolToVis}}"
HorizontalAlignment="Left" />
<ToggleButton Content="Edit" HorizontalAlignment="Right" VerticalAlignment="Center"
IsChecked="{Binding IsEditMode}"/>

Binding to CurrentItem in a ItemsControl

The XAML below is basically trying to make a list of Buttons (rendered from the Name property of objects in the Views collection in the current DataContext.
When I click on a button the CurrentItem property of CollectionViewSource should change and the associated View should be displayed in a content presenter.
OK. If I click in the ListBox in the XAML below it works exactly as desired.
But, If I click a button in the UniformGrid (created by the items control) the CurrentItem property is not updated.
How do I get the CurrentItem to be updated when an item is selected in the ItemsControl?
Thanks
<UserControl x:Class="Pos.Features.Reservation.ReservationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:product="clr-namespace:Pos.Features.ProductBrowser"
xmlns:activity="clr-namespace:Pos.Features.ActivityBrowser"
xmlns:addbysku="clr-namespace:Pos.Features.AddBySku"
xmlns:client="clr-namespace:Pos.Features.ClientBrowser"
xmlns:notes="clr-namespace:Pos.Features.Notes"
xmlns:controls="clr-namespace:Pos.Views"
xmlns:res="clr-namespace:Pos.Core;assembly=Pos.Core"
Height="300" Width="300">
<UserControl.Resources>
<DataTemplate DataType="{x:Type product:ProductBrowserViewModel}">
<product:ProductBrowserView/>
</DataTemplate>
<DataTemplate DataType="{x:Type activity:ActivityBrowserViewModel}">
<activity:ActivityBrowserView/>
</DataTemplate>
<CollectionViewSource x:Name="x" x:Key="ViewsCollection" Source="{Binding Views}" />
</UserControl.Resources>
<StackPanel>
<ListBox Name="ListBoxMenu" Grid.Column="0" Margin="5" ItemsSource="{Binding Source={StaticResource ViewsCollection}}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Padding="10"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Column="1" Content="{Binding ElementName=ListBoxMenu, Path=SelectedItem}"/>
<ItemsControl Grid.Column="2" Name="ViewList" ItemsSource="{Binding Source={StaticResource ViewsCollection}}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button>
<TextBlock Text="{Binding Path=Name}" Name="txtButtonLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"/>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="1" Columns="{Binding Views.Count}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<ContentControl Grid.Column="3" Content="{Binding Source={StaticResource ViewsCollection}, Path=CurrentItem}"/>
<Button Grid.Column="4" Click="Button_Click">dsadsd</Button>
</StackPanel>
</UserControl>
Your button does nothing. Usually your ViewModel would have an ICommand called Select (or something similar) that the Button would be bound against
Command="{Binding Select, ElementName="root"}"
and you'd pass the instance to the ICommand that you'd like to select
CommandParameter="{Binding}"
It would look something like this (c#/XAML like pseudocode):
public class MyModel { public string Name {get;set;} }
public class MyViewModel
{
public ObservableCollection<MyModel> Models {get;set;}
public ICommand Select {get;set;}
/* configure Models and Select etc */
}
<UserControl DataContext="{StaticResource MyViewModelInstance}" x:Name="root">
<ItemsControl ItemsSource="{Binding Models}">
<ItemsControl.ItemTemplate>
<ItemsPanelTemplate>
<Button Text="{Binding Name}"
Command="{Binding Select, ElementName="root"}"
CommandParameter="{Binding}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
The ItemsControl binds to Models, so each MyModel in Models gets a button. The button text is bound to the property Name. The button command is bound to the Select property in the ViewModel. When the button is pressed, it calls the ICommand, sending in the instance of MyModel that the button is bound against.
Please do note that using ViewModels within a UserControl is a code smell. UserControls should appear to users as all other controls--they should have bindable public properties which are bound to the user's ViewModel, not yours. You then bind to the values of these properties within the UserControl. For this example, you would have an ItemsSource property defined on your UserControl, and the ItemsControl would bind to this property rather than a ViewModel directly.
I think you have to do it manually in code-behind.
XAML
<Button Click="ViewListButton_Click">
<TextBlock Text="{Binding Path=Name}" Name="txtButtonLabel" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="Black"/>
</Button>
Code-behind
private void ViewListButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
ICollectionView view = CollectionViewSource.GetDefaultView(ViewList.ItemsSource);
view.MoveCurrentTo(button.DataContext);
}
If you're using the MVVM pattern and/or you don't want to use code-behind, you could bind the button's Command to a command in your ViewModel, as suggested by Will

Any way to reuse Bindings in WPF?

I'm getting to the point in a WPF application where all of the bindings on my controls are getting quite repetitive and also a little too verbose. Also if I want to change this binding I would have to change it in various places instead of just one.
Is there any way to write the source part of the binding once such as in a resource and then reuse it by referencing it with a more compact syntax. I've looked around for such capabilities but I haven't found it.
What I'm doing now
<StackPanel>
<ToggleButton x:Name="someToggleButton" />
<Button Visibility="{Binding ElementName=someToggleButton, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}" />
<Grid Visibility="{Binding ElementName=someToggleButton, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBox Visibility="{Binding ElementName=someToggleButton, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}" />
<CheckBox Visibility="{Binding ElementName=someToggleButton, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
What I want to be able to do (Pseudocode)
<StackPanel>
<StackPanel.Resources>
<Variable x:Name="someToggleButtonIsChecked"
Type="{x:Type Visibility}"
Value="{Binding ElementName=someToggleButton, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel.Resources>
<ToggleButton x:Name="someToggleButton" />
<Button Visibility="{VariableBinding someToggleButtonIsChecked}" />
<Grid Visibility="{VariableBinding someToggleButtonIsChecked}" />
<TextBox Visibility="{VariableBinding someToggleButtonIsChecked}" />
<CheckBox Visibility="{VariableBinding someToggleButtonIsChecked}" />
</StackPanel>
Is there any similar type of similar feature or technique that will allow me to declare the binding source once and then reuse it?
You can just bind someToggleButton's IsChecked property to a property on your viewmodel (the DataContext) and use that. It would look something like this:
<StackPanel>
<ToggleButton x:Name="someToggleButton" IsChecked="{Binding ToggleVisibility, Mode=TwoWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<Button Visibility="{Binding ToggleVisibility}" />
<Grid Visibility="{Binding ToggleVisibility}" />
<TextBox Visibility="{Binding ToggleVisibility}" />
<CheckBox Visibility="{Binding ToggleVisibility}" />
</StackPanel>
This would require that your Window's DataContext has a property called ToggleVisibility of type Visibility.
EDIT:
To eleborate further, your viewmodel could look like this:
public class SomeViewModel : INotifyPropertyChanged
{
private Visibility toggleVisibility;
public SomeViewModel()
{
this.toggleVisibility = Visibility.Visible;
}
public Visibility ToggleVisibility
{
get
{
return this.toggleVisibility;
}
set
{
this.toggleVisibility = value;
RaisePropertyChanged("ToggleVisibility");
}
}
private void RaisePropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
And you would then set an instance of it as the DataContext on the Window or even just on the StackPanel
Is there any way to write the source part of the binding once such as in a resource and then reuse it by referencing it with a more compact syntax.
Perhaps you can do that with PyBinding. I don't know the extent of its capabilities, but I use it all the time ot avoid type converters. Here is an example I use a lot.
Visibility="{p:PyBinding BooleanToVisibility(IsNotNull($[.InstanceName]))}"
BooleanToVisibility is a function I wrote in IronPython.
$[.InstanceName] binds to the InstanceName property of the current data-bound item.
EDIT: You can also use this to bind one UI's property to another's. Here is some info from the help file.
$[NameTextBlock.Text] - The text property of the element with x:Name equal to "NameTextBlock"
$[NameTextBlock] - An actual TextBlock instance, rather than one of its properties
$[{Self}] - Bind to your self. Equivalent to {Binding RelativeSource={RelativeSource Self}}
$[{Self}.Text] - The Text property off your self. Equivalent to {Binding Path=Text, RelativeSource={RelativeSource Self}}
http://pybinding.codeplex.com/
Untested Theory
<StackPanel>
<ToggleButton x:Name="someToggleButton" />
<Button Visibility="{p:PyBinding BooleanToVisibility($[someToggleButton.IsChecked])}" />
<Grid Visibility="{p:PyBinding BooleanToVisibility($[someToggleButton.IsChecked])}"/>
<TextBox Visibility="{p:PyBinding BooleanToVisibility($[someToggleButton.IsChecked])}"/>
<CheckBox Visibility="{p:PyBinding BooleanToVisibility($[someToggleButton.IsChecked])}"/>
</StackPanel>
Second Attempt
<StackPanel>
<ToggleButton x:Name="someToggleButton" />
<Button Name="myButton" Visibility="{p:PyBinding BooleanToVisibility($[someToggleButton.IsChecked])}" />
<Grid Visibility="{p:PyBinding $[myButton.Visibility]}"/>
<TextBox Visibility="{p:PyBinding $[myButton.Visibility]}"/>
<CheckBox Visibility="{p:PyBinding $[myButton.Visibility]}"/>
</StackPanel>
Just looking at the original code, you could group the necessary elements into their own container and then manage the container Visibility:
<StackPanel>
<ToggleButton x:Name="someToggleButton" />
<StackPanel Visibility="{Binding ElementName=someToggleButton, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}}">
<Button />
<Grid />
<TextBox />
<CheckBox />
</StackPanel>
</StackPanel>
Actually, today I would do this with the VSM - have a state with the elements Visible and a state with them not Visible, then use two GoToState Behaviors on the Toggle button to set the state based on the button's toggle state.

Resources