Grid Column changing Width when animating - wpf

I have a Grid element that has two columns and three rows. The last row has a height of 0... and I animate the height property using a custom animation class because the gridheight property isnt an integer..
The animation works just fine, but when I activate it, it changes the width of the second column seemingly randomly.. sometimes just a few pixels bigger and sometimes over double as wide...
Here is the grid code
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="7"/>
<RowDefinition Name="LyricsRow" Height="1">
<RowDefinition.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsTrayOpen}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<local:GridLengthAnimation
Storyboard.TargetProperty="Height"
From="0" To="150" Duration="0:0:0.3" >
</local:GridLengthAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<local:GridLengthAnimation
Storyboard.TargetProperty="Height"
From="150" To="0" Duration="0:0:0.5" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
</Grid.RowDefinitions>
Is there any reasons this could be going on?

For those of you that might be wondering about the GridLengthAnimation implementation mentioned in the question, here is one (from http://social.msdn.microsoft.com/forums/en-US/wpf/thread/da47a4b8-4d39-4d6e-a570-7dbe51a842e4/)
/// <summary>
/// Animates a grid length value just like the DoubleAnimation animates a double value
/// </summary>
public class GridLengthAnimation : AnimationTimeline
{
/// <summary>
/// Returns the type of object to animate
/// </summary>
public override Type TargetPropertyType
{
get
{
return typeof(GridLength);
}
}
/// <summary>
/// Creates an instance of the animation object
/// </summary>
/// <returns>Returns the instance of the GridLengthAnimation</returns>
protected override System.Windows.Freezable CreateInstanceCore()
{
return new GridLengthAnimation();
}
/// <summary>
/// Dependency property for the From property
/// </summary>
public static readonly DependencyProperty FromProperty = DependencyProperty.Register("From", typeof(GridLength),
typeof(GridLengthAnimation));
/// <summary>
/// CLR Wrapper for the From depenendency property
/// </summary>
public GridLength From
{
get
{
return (GridLength)GetValue(GridLengthAnimation.FromProperty);
}
set
{
SetValue(GridLengthAnimation.FromProperty, value);
}
}
/// <summary>
/// Dependency property for the To property
/// </summary>
public static readonly DependencyProperty ToProperty = DependencyProperty.Register("To", typeof(GridLength),
typeof(GridLengthAnimation));
/// <summary>
/// CLR Wrapper for the To property
/// </summary>
public GridLength To
{
get
{
return (GridLength)GetValue(GridLengthAnimation.ToProperty);
}
set
{
SetValue(GridLengthAnimation.ToProperty, value);
}
}
/// <summary>
/// Animates the grid let set
/// </summary>
/// <param name="defaultOriginValue">The original value to animate</param>
/// <param name="defaultDestinationValue">The final value</param>
/// <param name="animationClock">The animation clock (timer)</param>
/// <returns>Returns the new grid length to set</returns>
public override object GetCurrentValue(object defaultOriginValue,
object defaultDestinationValue, AnimationClock animationClock)
{
double fromVal = ((GridLength)GetValue(GridLengthAnimation.FromProperty)).Value;
//check that from was set from the caller
if (fromVal == 1)
//set the from as the actual value
fromVal = ((GridLength)defaultDestinationValue).Value;
double toVal = ((GridLength)GetValue(GridLengthAnimation.ToProperty)).Value;
if (fromVal > toVal)
return new GridLength((1 - animationClock.CurrentProgress.Value) * (fromVal - toVal) + toVal, GridUnitType.Star);
else
return new GridLength(animationClock.CurrentProgress.Value * (toVal - fromVal) + fromVal, GridUnitType.Star);
}
}

It probably depends on the content of the "cells"
try setting MaxWidth="50" on your columnDefinition in addition to Width="50"

Related

SelectAll text on Custom TextBox control in a DataGrid on CellEditingTemplate

The question says it all: I can use Events to select all the text in a DataGrid Custom TextBox, BUT it does not work when the TextBox is initially created (i.e. When the cell enters edit mode and create the TextBox).
IF I click in the TextBox after it is created the text is fully selected, but it should already be selected after the TextBox is displayed. This does not work. I tried setting Focus in Code, or using FocusManager in XAML but not helps.
Here is the Code (less the Dependency Properties):
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ccont = "clr-namespace:App.Controls">
<!-- Default style for the Validation Buttons -->
<Style TargetType="{x:Type ccont:vokDataGridEdit}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ccont:vokDataGridEdit}">
<TextBox Text = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
BorderThickness = "0"
ContextMenuService.Placement = "Right"
ContextMenuService.PlacementTarget = "{Binding Path=., RelativeSource={RelativeSource Self}}"
x:Name = "TextBox">
<TextBox.ContextMenu>
<ContextMenu x:Name="Menu">
<ContextMenu.Template>
<ControlTemplate>
<Border CornerRadius = "5"
Background = "LightGray"
BorderThickness = "1"
BorderBrush = "Gray"
Padding = "2">
<StackPanel Orientation="Vertical">
<!-- Title -->
<TextBlock Text="Test" x:Name = "Test" />
<!-- TODO: List of matches -->
<TextBox Text = "{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"
BorderThickness = "0" />
</StackPanel>
</Border>
</ControlTemplate>
</ContextMenu.Template>
</ContextMenu>
</TextBox.ContextMenu>
</TextBox>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And Code (Dependency Properties not shown):
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace App.Controls
{
/// <summary>
/// DataGrid Edit control (see: https://www.c-sharpcorner.com/article/wpf-routed-events/ for RoutedEvents)
/// </summary>
public class vokDataGridEdit : Control
{
static vokDataGridEdit()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(vokDataGridEdit), new FrameworkPropertyMetadata(typeof(vokDataGridEdit)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// Demo purpose only, check for previous instances and remove the handler first
if (this.GetTemplateChild("TextBox") is TextBox button)
{
button.PreviewMouseLeftButtonDown += this.SelectContentPreparation;
button.GotKeyboardFocus += this.SelectContent;
button.MouseDoubleClick += this.SelectContent;
//button.GotFocus += this.SelectContent;
}
}
/// <summary>
/// Prepare the Control to ensure it has focus before subsequent event fire
/// </summary>
private void SelectContentPreparation(object sender, MouseButtonEventArgs e)
{
if (sender is TextBox tb)
{
if (!tb.IsKeyboardFocusWithin)
{
e.Handled = true;
tb.Focus();
}
}
}
private void SelectContent(object sender, RoutedEventArgs e)
{
if (sender is TextBox tb)
{
e.Handled = true;
tb.SelectAll();
}
}
}
}
Ok finaly using a Behavior solved my issue in conjunction with using Events on the CustomControl. I still have no clues why it did not work using Events though...
CustomControl XAML:
<ResourceDictionary xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:b = "http://schemas.microsoft.com/xaml/behaviors"
xmlns:ccont = "clr-namespace:App.Controls"
xmlns:res = "clr-namespace:App.Controls.Resources"
xmlns:valid = "clr-namespace:App.Controls.Validation"
xmlns:conv = "clr-namespace:Common.MVVM.Converter;assembly=Common.MVVM"
xmlns:behav = "clr-namespace:Common.MVVM.Behavior;assembly=Common.MVVM">
<!-- Default style for the Validation Buttons -->
<Style TargetType="{x:Type ccont:vokDataGridEdit}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ccont:vokDataGridEdit}">
<Grid>
<!-- !!! The Value edited !!! -->
<TextBox BorderThickness = "0"
x:Name = "textBox">
<!-- Create a binding proxy to serve binding properties to data validation Binding Wrapper
see: https://social.technet.microsoft.com/wiki/contents/articles/31422.wpf-passing-a-data-bound-value-to-a-validation-rule.aspx -->
<TextBox.Resources>
<valid:BindingProxy x:Key="proxy" Context="{Binding HintsInternal, RelativeSource={RelativeSource AncestorType=ccont:vokDataGridEdit}}"/>
</TextBox.Resources>
<!-- Bind with data validation -->
<TextBox.Text>
<Binding RelativeSource = "{RelativeSource AncestorType=ccont:vokDataGridEdit}"
Path = "Text"
Mode = "TwoWay"
UpdateSourceTrigger = "PropertyChanged"
ValidatesOnExceptions = "True">
<Binding.ValidationRules>
<valid:vokDataGridEditValidation>
<valid:vokDataGridEditValidation.Bindings>
<valid:vokDataGridEditValidationBindings InvalidEntries="{Binding Context, Source={StaticResource proxy}}" />
</valid:vokDataGridEditValidation.Bindings>
</valid:vokDataGridEditValidation>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<!-- Select all text on focus -->
<b:Interaction.Behaviors>
<behav:TextBoxSelectAllBehavior />
</b:Interaction.Behaviors>
</TextBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
And CustomControl event declarations:
/// <summary>
/// DataGrid Edit control (see: https://www.c-sharpcorner.com/article/wpf-routed-events/ for RoutedEvents)
/// </summary>
public class vokDataGridEdit : Control
{
#region Initialization
static vokDataGridEdit()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(vokDataGridEdit), new FrameworkPropertyMetadata(typeof(vokDataGridEdit)));
}
/// <summary>
/// Set event handlers when applying the template
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
// OnLoaded set focus to the TextBox (lambda and own Event need not Garbage Collection) and ensure it has focus for new text input
if (this.GetTemplateChild("textBox") is TextBox textBox)
{
this.Loaded += (sender, e) => { textBox.Focus(); };
this.PreviewTextInput += (sender, e) => { textBox.Focus(); };
}
}
#endregion
// Skipping Dependency Properties
}
And Finaly the behavior based on an Answer by user Rekshino here (https://stackoverflow.com/a/60844159/12886393):
/// <summary>
/// Behavior for control to select all text in TextBox on GotFocus
/// </summary>
public class TextBoxSelectAllBehavior : Behavior<TextBox>
{
/// <summary>
/// Flag marking if Selection is to be performed on MouseUp
/// </summary>
private bool _doSelectAll = false;
/// <summary>
/// Setup the behavior
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.GotFocus += this.AssociatedObject_GotFocus;
this.AssociatedObject.PreviewMouseUp += this.AssociatedObject_MouseUp;
this.AssociatedObject.PreviewMouseDown += this.AssociatedObject_MouseDown;
}
/// <summary>
/// Select all via dispatcher if action set for MouseUp
/// </summary>
private void AssociatedObject_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (this._doSelectAll)
{
this.AssociatedObject.Dispatcher.BeginInvoke((Action)(() => { this.AssociatedObject.SelectAll(); }));
}
this._doSelectAll = false;
}
/// <summary>
/// Triggers SelectAll on mouse up if focus was not set
/// </summary>
private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
this._doSelectAll = !this.AssociatedObject.IsFocused;
}
/// <summary>
/// Selects all
/// </summary>
private void AssociatedObject_GotFocus(object sender, System.Windows.RoutedEventArgs e)
{
this.AssociatedObject.SelectAll();
}
/// <summary>
/// Clean-up the behavior
/// </summary>
protected override void OnDetaching()
{
this.AssociatedObject.GotFocus -= this.AssociatedObject_GotFocus;
this.AssociatedObject.PreviewMouseUp -= this.AssociatedObject_MouseUp;
this.AssociatedObject.PreviewMouseDown -= this.AssociatedObject_MouseDown;
base.OnDetaching();
}
}

DataTriggers and default property when using ViewModels, Dependency Properties

I have a UserControl and a ViewModel for it.
The UserControl is a simple Button Template, with an Ellipse in it. Basically a round button.
I use the ViewModel as a DataContext for the UserControl.
The ViewModel has a property called "State" whose value I use as a DataTrigger to change "Colors" of the Ellipse, these "Colors" are Dependency Properties in the UserControl.
Everything seems to work, but I cannot set the Default Colors for the Ellipse and because of that I don`t see anything in the Designer for the UserControl. See attached picture.
I do see the correct colors and the shapes when I place the UserControl on to the main window designer.
I definitely would like to see the shapes in the UserControl with some default values, so its easier to see what I`m working with.
This I believe is something to do with DataBinding "State" value from the ViewModel.
Here is the Code : UserControl xaml
<UserControl x:Name="thisBtn" x:Class="WpfAppDelMe.Views.CustomButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfAppDelMe.Views"
xmlns:viewmodel="clr-namespace:WpfAppDelMe.ViewModels"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.DataContext>
<viewmodel:CustButtonViewModel x:Name="xvm"/>
</UserControl.DataContext>
<Grid Background="#FF9EE3EA">
<Button Content="{Binding State}" Command="{Binding ClickCommand}" Foreground="#FFD13B3B" Margin="0" >
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Viewbox Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" Grid.ColumnSpan="3">
<Ellipse Height="300" Width="300" Margin="5">
<Ellipse.Style>
<Style TargetType="{x:Type Ellipse}" >
<!-- Below Line does not work! -->
<Setter Property ="Fill" Value="Pink" />
<Style.Triggers>
<DataTrigger Binding="{Binding State}" Value="{x:Null}">
<Setter Property="Fill" Value="OldLace"/>
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="0">
<Setter Property="Fill" Value="{Binding ElementName=thisBtn, Path=Color1}"/>
</DataTrigger>
<DataTrigger Binding="{Binding State}" Value="1">
<Setter Property="Fill" Value="{Binding ElementName=thisBtn, Path=Color2}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
</Viewbox>
<!--The content presenter for the button string.
Placing this in a view box makes sure of the correct size.-->
<Viewbox Grid.Row="1" Grid.ColumnSpan="3" Margin="10">
<ContentPresenter/>
</Viewbox>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
UserControl .cs file
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfAppDelMe.Views
{
/// <summary>
/// Interaction logic for CustXam.xaml
/// </summary>
public partial class CustomButton : UserControl, INotifyPropertyChanged
{
/// <summary>
/// Interaction logic for the CustomButton UserControl
/// View and Model
/// ViewModel : CustomButtonViewModel
/// </summary>
public CustomButton()
{
InitializeComponent();
}
public static readonly DependencyProperty Color1Property =
DependencyProperty.Register("Color1", typeof(Brush), typeof(CustButton), new
PropertyMetadata(new SolidColorBrush(Colors.Black)));
public static readonly DependencyProperty Color2Property =
DependencyProperty.Register("Color2", typeof(Brush), typeof(CustButton), new
PropertyMetadata(new SolidColorBrush(Colors.White)));
public event PropertyChangedEventHandler PropertyChanged = delegate { };
//Null initialization is required because there are no listeners or
bindings
for this.
private int btnState=0;
//Button Color 1
public Brush Color1
{
get { return (Brush)GetValue(Color1Property); }
set { SetValue(Color1Property, value); }
}
public Brush Color2
{
get { return (Brush)GetValue(Color2Property); }
set { SetValue(Color2Property, value); }
}
public int BtnState
{
get
{ return btnState;}
set
{
btnState = value;
this.OnPropertyChanged("BtnState");
}
}
public void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
xvm.State = btnState;
}
}
}
}
[![enter image description here][1]][1]ViewModel for the UseControl
using System;
using System.ComponentModel;
using System.Windows.Input;
namespace WpfAppDelMe.ViewModels
{
/// <summary>
/// View Model class for the CustButton. All logic and calculations happen
here.
/// This is also the DataContext for the CustButton
///
/// View and Model : CustButton
/// ViewModel : CustButtonViewModel
/// </summary>
internal class CustButtonViewModel : INotifyPropertyChanged
{
public CustButtonViewModel()
{
}
private int state;
public event PropertyChangedEventHandler PropertyChanged;
private ICommand clickCommand;
public ICommand ClickCommand
{
get
{
if (clickCommand == null)
{
clickCommand = new RelayCommand(param => ChangeState(), param => CanChange());
}
return clickCommand;
}
}
/// <summary>
///
/// </summary>
private void ChangeState()
{
if (State == 0)
{
State = 1;
}
else
{
State = 0;
}
}
private bool CanChange()
{
return true;
}
//Button state
public int State
{
get{ return state; }
set
{
state = value;
OnPropertyChange("State");
}
}
/// <summary>
/// On Property Changed
/// </summary>
/// <param name="name"></param>
public void OnPropertyChange(string prop)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(prop));
}
}
}
}

Bind a button to the selected item

I have a button and want to change the click handler every time I change tabs. I was hoping to perform this with Binding.
<Window x:Class="BWCRenameUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vpan="clr-namespace:BWCRenameUtility.View.VersionPanels"
Title="MainWindow" Height="526" Width="525">
<Grid>
<DockPanel>
<TextBlock Text="Foo" DockPanel.Dock="Top" TextWrapping="Wrap" Padding="10" />
<Grid DockPanel.Dock="Bottom">
<!-- This is not correct, how do I perform this binding correct? -->
<Button Content="Export..." HorizontalAlignment="Right" Margin="10" Click="{Binding SelectedItem.Content.PerformExport,ElementName=tabcontrol}" />
</Grid>
<TabControl Name="tabcontrol">
<TabItem Header="1.2.5">
<vpan:VersionPanel1_2_5/>
</TabItem>
<TabItem Header="1.2.8">
<vpan:VersionPanel1_2_8/> <!-- These can be of the same Type by inheritance -->
</TabItem>
</TabControl>
</DockPanel>
</Grid>
</Window>
As you can see, the Button.Click is not bound correctly and I want to know how this works in WPF.
You can achieve this with Commands, you will create a ICommand for each of toy TabItem ViewModels and bind the Buttons Command property to that Command
The RelayCommand is a very common way to handle stuff like this and can be used throughout your application
Relay command:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The execute.</param>
public RelayCommand(Action<object> execute) : this(execute, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The action to execute.</param>
/// <param name="canExecute">The can execute.</param>
/// <exception cref="System.ArgumentNullException">execute</exception>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion
#region ICommand Members
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion
}
And you would use in the following fashion in your application
ViewModel or Control:
public class VersionPanel1_2_8 : VersionPanel
{
public ICommand MyCommand { get; internal set; }
public VersionPanel1_2_8()
{
MyCommand = new RelayCommand(x => MethodToExecute());
}
private void MethodToExecute()
{
}
}
Xaml:
<Window x:Class="BWCRenameUtility.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vpan="clr-namespace:BWCRenameUtility.View.VersionPanels"
Title="MainWindow" Height="526" Width="525">
<Grid>
<DockPanel>
<TextBlock Text="Foo" DockPanel.Dock="Top" TextWrapping="Wrap" Padding="10" />
<Grid DockPanel.Dock="Bottom">
<!-- This is not correct, how do I perform this binding correct? -->
<Button Content="Export..." HorizontalAlignment="Right" Margin="10" Command="{Binding SelectedItem.Content.MyCommand,ElementName=tabcontrol}" />
</Grid>
<TabControl Name="tabcontrol">
<TabItem Header="1.2.5">
<vpan:VersionPanel1_2_5/>
</TabItem>
<TabItem Header="1.2.8">
<vpan:VersionPanel1_2_8/> <!-- These can be of the same Type by inheritance -->
</TabItem>
</TabControl>
</DockPanel>
</Grid>
</Window>
You need to bind Button's Command property to your command in WPF like,
Command="{Binding SelectedItem.Content.PerformExport, ElementName=tabcontrol}"
Click is an event, if you want you can also do event to command binding (binding any event to command in your viewmodel) however that is not necessary in your case

Silverlight XAML Binding ElementName

I have the following XAML:
<sdk:Label Content="{Binding RefreshTextToggle, Converter={StaticResource enumToText}, ConverterParameter=ItemsOfInterest,FallbackValue='Please select items of interest to you'}"
Style="{StaticResource StandardLabel}"
Height="{Binding ElementName=ItemsOfInterest,Path=Height}"/>
<ListBox Name="ItemsOfInterest"
ItemsSource="{Binding Path=ItemsOfInterest}"
Margin="5"
MinHeight="25"
Width="250"
HorizontalAlignment="Left">
The height of the ItemsOfInterest is dynamic pending on how many elements are in it.
Anyone see what I am doing wrong with the height binding? It isn't even close to the same size as the ItemsOfInterst.
You should bind to ActualHeight, which specifies the height it was arranged at. The Height property allows you to set a fixed height, but doesn't tell you exactly how tall it is when arranged.
EDIT:
Actually, this is a known bug with Silverlight.
You would have to use the SizeChanged event like so:
<UserControl x:Class="SilverlightApplication3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Add Item" Click="Button_Click" />
<ListBox x:Name="listBox1" Grid.Column="0" Grid.Row="1" VerticalAlignment="Top" SizeChanged="listBox1_SizeChanged" />
<ListBox x:Name="listBox2" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" />
</Grid>
</UserControl>
With a code-behind of:
using System.Windows;
using System.Windows.Controls;
namespace SilverlightApplication3 {
public partial class MainPage : UserControl {
public MainPage() {
InitializeComponent();
}
private int counter;
private void Button_Click(object sender, RoutedEventArgs e) {
this.counter++;
this.listBox1.Items.Add(counter.ToString());
}
private void listBox1_SizeChanged(object sender, SizeChangedEventArgs e) {
this.listBox2.Height = this.listBox1.ActualHeight;
}
}
}
You could probably wrap this up into a nice attached behavior also.
EDIT:
Here is such a behavior:
<UserControl x:Class="SilverlightApplication3.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:SilverlightApplication3"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Add Item" Click="Button_Click" />
<ListBox x:Name="listBox1" Grid.Column="0" Grid.Row="1" VerticalAlignment="Top" />
<ListBox x:Name="listBox2" Grid.Column="1" Grid.Row="1" VerticalAlignment="Top" local:SizeSynchronizationBehavior.HeightElement="{Binding ElementName=listBox1}" />
</Grid>
</UserControl>
With a code-behind of:
using System;
using System.Windows;
using System.Windows.Controls;
namespace SilverlightApplication3 {
public partial class MainPage : UserControl {
public MainPage() {
InitializeComponent();
}
private int counter;
private void Button_Click(object sender, RoutedEventArgs e) {
this.counter++;
this.listBox1.Items.Add(counter.ToString());
}
}
public static class SizeSynchronizationBehavior {
#region Dependency Properties
///////////////////////////////////////////////////////////////////////////////////
// HeightElement
///////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Identifies the <c>HeightElement</c> attached dependency property. This field is read-only.
/// </summary>
/// <value>The identifier for the <c>HeightElement</c> attached dependency property.</value>
public static readonly DependencyProperty HeightElementProperty = DependencyProperty.RegisterAttached("HeightElement",
typeof(FrameworkElement), typeof(SizeSynchronizationBehavior), new PropertyMetadata(null, OnHeightElementPropertyValueChanged));
/// <summary>
/// Gets the value of the <see cref="HeightElementProperty"/> attached property for the specified <see cref="FrameworkElement"/>.
/// </summary>
/// <param name="obj">The object to which the attached property is retrieved.</param>
/// <returns>
/// The value of the <see cref="HeightElementProperty"/> attached property for the the specified <see cref="FrameworkElement"/>.
/// </returns>
public static FrameworkElement GetHeightElement(FrameworkElement obj) {
if (obj == null) throw new ArgumentNullException("obj");
return (FrameworkElement)obj.GetValue(HeightElementProperty);
}
/// <summary>
/// Sets the value of the <see cref="HeightElementProperty"/> attached property to the specified <see cref="FrameworkElement"/>.
/// </summary>
/// <param name="obj">The object to which the attached property is written.</param>
/// <param name="value">
/// The new value of the <see cref="HeightElementProperty"/> attached property to the specified <see cref="FrameworkElement"/>.
/// </param>
public static void SetHeightElement(FrameworkElement obj, FrameworkElement value) {
if (obj == null) throw new ArgumentNullException("obj");
obj.SetValue(HeightElementProperty, value);
}
/// <summary>
/// Called when <see cref="HeightElementProperty"/> is changed.
/// </summary>
/// <param name="d">The dependency object that was changed.</param>
/// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void OnHeightElementPropertyValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
FrameworkElement element = d as FrameworkElement;
if (element == null)
return;
SizeChangedEventHandler heightSizeChangedEventHandler = GetSizeChangedEventHandler(element);
if (heightSizeChangedEventHandler == null) {
heightSizeChangedEventHandler = (sender, eventArgs) => {
FrameworkElement he = GetHeightElement(element);
if (he != null)
element.Height = he.ActualHeight;
};
SetSizeChangedEventHandler(element, heightSizeChangedEventHandler);
}
FrameworkElement heightElement = e.OldValue as FrameworkElement;
if (heightElement != null)
heightElement.SizeChanged += heightSizeChangedEventHandler;
heightElement = e.NewValue as FrameworkElement;
if (heightElement != null)
heightElement.SizeChanged += heightSizeChangedEventHandler;
}
///////////////////////////////////////////////////////////////////////////////////
// SizeChangedEventHandler
///////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Identifies the <c>SizeChangedEventHandler</c> attached dependency property. This field is read-only.
/// </summary>
/// <value>The identifier for the <c>SizeChangedEventHandler</c> attached dependency property.</value>
private static readonly DependencyProperty SizeChangedEventHandlerProperty = DependencyProperty.RegisterAttached("SizeChangedEventHandler",
typeof(SizeChangedEventHandler), typeof(SizeSynchronizationBehavior), new PropertyMetadata(null));
/// <summary>
/// Gets the value of the <see cref="SizeChangedEventHandlerProperty"/> attached property for the specified <see cref="FrameworkElement"/>.
/// </summary>
/// <param name="obj">The object to which the attached property is retrieved.</param>
/// <returns>
/// The value of the <see cref="SizeChangedEventHandlerProperty"/> attached property for the the specified <see cref="FrameworkElement"/>.
/// </returns>
private static SizeChangedEventHandler GetSizeChangedEventHandler(FrameworkElement obj) {
if (obj == null) throw new ArgumentNullException("obj");
return (SizeChangedEventHandler)obj.GetValue(SizeChangedEventHandlerProperty);
}
/// <summary>
/// Sets the value of the <see cref="SizeChangedEventHandlerProperty"/> attached property to the specified <see cref="FrameworkElement"/>.
/// </summary>
/// <param name="obj">The object to which the attached property is written.</param>
/// <param name="value">
/// The new value of the <see cref="SizeChangedEventHandlerProperty"/> attached property to the specified <see cref="FrameworkElement"/>.
/// </param>
private static void SetSizeChangedEventHandler(FrameworkElement obj, SizeChangedEventHandler value) {
if (obj == null) throw new ArgumentNullException("obj");
obj.SetValue(SizeChangedEventHandlerProperty, value);
}
#endregion // Dependency Properties
}
}
I have created a simple project which replicates the problem you are experiencing:
Added a listbox and button
Hooked the button to the height of the listbox
Set the listbox height to "Auto"
Created another button which adds a random amount of items to the Listbox
On run, the listbox grows, but the associated button does not. I am now working to determine why this fails.
UPDATE
Okay, the reason why this does not work is that Auto does not return any actual value that can be used to determine the height of the listbox by this binding. I tried every other approach to solve this without success. It will be interesting to see the resolution of this problem while just relying on binding.

TabItem header click

I have defined a control template/style for my tab items as follows:
<Style x:Key="TabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding Content.DataContext.Header, RelativeSource={RelativeSource Self}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid Width="Auto" Height="Auto" x:Name="TabItemRoot" Margin="10,0,10,0">
<Button Command="{Binding Content.DataContext.HeaderClickedCommand}">
<ContentPresenter Margin="13,5,13,5"
x:Name="Content"
ContentSource="Header"
RecognizesAccessKey="True">
</ContentPresenter>
</Button>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
When I click on the tab header the command bound to the button is called OK, however, the click event appears to have eaten the SelectionChanged event and therefore the tab page doesn't change.
Is there a better way to implement this so I can call a VM method and get still get the tab page to change?
Update as per comment
The idea being that if the user clicks the header of the currently active tab it updates the active tabs content through changes in the VM. Thanks
Here is my way to implement such functionality, but whether it is better or not - it's matter of taste.
The main xaml looks so:
<TabControl ItemsSource="{Binding TabItems}">
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Title}"/>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:ExecuteCommandAction Command="{Binding HeaderClickCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
</TabControl>
There is no ControlTemplate, just DataTemplate with the attached property Interaction.Triggers, where the prefix i is defined in this string:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
This library can be found in MS Blend SDK or with the library mvvm light (on codeplex).
Also, don't confuse EventTriggers that has the prefix i and generic EventTriggers, they are different at some point, but I don't know exactly what the difference is, except that the EventTrigger class from the custom library work with Silverlight too.
In my example the trigger is subscribed to the event MouseLeftButtonDown and calls the special action class every time when the event is raised.
This action class is a custom class and it is defined in code:
/// <summary>
/// Behaviour helps to bind any RoutedEvent of UIElement to Command.
/// </summary>
[DefaultTrigger(typeof(UIElement), typeof(System.Windows.Interactivity.EventTrigger), "MouseLeftButtonDown")]
public class ExecuteCommandAction : TargetedTriggerAction<UIElement>
{
/// <summary>
/// Dependency property represents the Command of the behaviour.
/// </summary>
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached("CommandParameter",
typeof(object), typeof(ExecuteCommandAction), new FrameworkPropertyMetadata(null));
/// <summary>
/// Dependency property represents the Command parameter of the behaviour.
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command",
typeof(ICommand), typeof(ExecuteCommandAction), new FrameworkPropertyMetadata(null));
/// <summary>
/// Gets or sets the Commmand.
/// </summary>
public ICommand Command
{
get
{
return (ICommand)this.GetValue(CommandProperty);
}
set
{
this.SetValue(CommandProperty, value);
}
}
/// <summary>
/// Gets or sets the CommandParameter.
/// </summary>
public object CommandParameter
{
get
{
return this.GetValue(CommandParameterProperty);
}
set
{
this.SetValue(CommandParameterProperty, value);
}
}
/// <summary>
/// Invoke method is called when the given routed event is fired.
/// </summary>
/// <param name="parameter">
/// Parameter is the sender of the event.
/// </param>
protected override void Invoke(object parameter)
{
if (this.Command != null)
{
if (this.Command.CanExecute(this.CommandParameter))
{
this.Command.Execute(this.CommandParameter);
}
}
}
}
That is all. Now the command doesn't prevent the tabcontrol from selection of items.
Code-behind to test this xaml:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var items = new ObservableCollection<TabItemViewModel>
{
new TabItemViewModel("Item 1"), new TabItemViewModel("Item 2"), new TabItemViewModel("Item 3")
};
this.DataContext = new MainViewModel(){TabItems = items};
}
}
public class MainViewModel
{
public ObservableCollection<TabItemViewModel> TabItems { get; set; }
}
public class TabItemViewModel
{
public TabItemViewModel(string title)
{
this.Title = title;
this.HeaderClickCommand = new RelayCommand(() => MessageBox.Show("Clicked "+this.Title));
}
public string Title { get; set; }
public RelayCommand HeaderClickCommand { get; set; }
}
To call the command only when an item is selected, change this code:
<local:ExecuteCommandAction Command="{Binding HeaderClickCommand}"
CommandParameter="{Binding IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType=TabItem}}"/>
And this (the second parameter is the CanExecute delegate, it checks IsSelected == true):
this.HeaderClickCommand = new RelayCommand<bool>(b => {/*???*/}, b => b == true);

Resources