I'm struggling with some databinding. In my MainWindow I have 2 buttons which are databound to a property Result in a class "Properties" - strictly for holding properties that I will be using for databinding. The buttons are hidden by default, and when I want them to become visible I simply set Result property that they are bound to to "True"
I know the databinding is working because if I set the property to a static value, the buttons are visible / not visible. See below for my XAML
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</StackPanel.Resources>
<Button x:Name="btnBack" Height="25" Content="<- Back" Visibility="{Binding Path=Result, Converter={StaticResource BoolToVis},
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="75" Click="btnBack_Click" Margin="0,0,10,0" />
<Button x:Name="btnNext" Height="25" Content="Next ->" Visibility="{Binding Path=Result, Converter={StaticResource BoolToVis},
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="75" Click="btnNext_Click" Margin="10,0,0,0"/>
</StackPanel>
So they are bound to "Result" property, and I have UpdateSourceTrigger=Propertychangedin my binding.
In my "Properties" class I have the below and AM implementing INotifyPropertyChanged
bool _result;
#endregion
public bool Result {
get
{
return _result;
}
set
{
_result = value;
NotifyPropertyChanged("Result");
}
}
#region EVENTS
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
But for some reason when I change the property to "True" the PropertyChanged Event is null and therefore the event never fires.
Any idea as to why this is happening? Could it be because this code isn't in my ViewModel and just in a separate class?
Make sure that you have set the DataContext of the window to an instance of your Properties class and that you don't set the DataContext property of any parent element of the StackPanel to something else because the DataContext is inherited.
Please refer to the following sample code. The Buttons do become visible as expected after the 3 second delay:
public partial class MainWindow : Window
{
private Properties _viewModel = new Properties();
public MainWindow()
{
InitializeComponent();
DataContext = _viewModel;
this.Loaded += async (s, e) =>
{
await Task.Delay(3000);
_viewModel.Result = true;
};
}
}
public class Properties : INotifyPropertyChanged
{
bool _result;
public bool Result
{
get
{
return _result;
}
set
{
_result = value;
NotifyPropertyChanged("Result");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
<Window x:Class="WpfApplication1.MainWindow"
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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="300" Width="300">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<StackPanel.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</StackPanel.Resources>
<Button x:Name="btnBack" Height="25" Content="<- Back" Visibility="{Binding Path=Result, Converter={StaticResource BoolToVis},
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="75" Margin="0,0,10,0" />
<Button x:Name="btnNext" Height="25" Content="Next ->" Visibility="{Binding Path=Result, Converter={StaticResource BoolToVis},
UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="75" Margin="10,0,0,0"/>
</StackPanel>
</Window>
Related
I am developing a small application for learning purpose. I find that when I bind ItemControl's ItemSource to a ViewModel property in XAML, it doesn't work in an expected way. i.e. It loads the underlying collection with values at the loading time, but any changes to it are not reflected.
However, if I set Itemsource in Codebehind, it works.
When the form is loaded, it shows 2 note objects. Clicking on button should show the 3rd one. I don't understand why setting DataContext using XAML doesn't update to changes in collection. I am sharing snippet of the code here. Any help greatly appreciated.
Cut-down version of XAML -
<Window x:Class="NotesApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NotesApp"
xmlns:vm="clr-namespace:NotesApp.ViewModel"
Title="MainWindow" Height="480" Width="640">
<Window.DataContext >
<vm:MainViewModel/>
</Window.DataContext>
<DockPanel >
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl Name="NoteItemControl" ItemsSource="{Binding notes}" Background="Beige" >
<ItemsControl.LayoutTransform>
<ScaleTransform ScaleX="{Binding Value, ElementName=zoomSlider}" ScaleY="{Binding Value, ElementName=zoomSlider}" />
</ItemsControl.LayoutTransform>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Name="NoteBorder" Background="Green" CornerRadius="3" Margin="5,3,5,3">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding noteText}" Margin="5,3,5,3"/>
<StackPanel Grid.Row="1" Orientation="Vertical" >
<Line X1="0" Y1="0" X2="{Binding ActualWidth,ElementName=NoteBorder}" Y2="0" Stroke="Black" StrokeThickness="1"/>
<TextBlock Text="{Binding Category}" Margin="5,3,5,3"/>
</StackPanel>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</DockPanel>
</Window>
View Code behind-
namespace NotesApp
{
public partial class MainWindow : Window
{
MainViewModel ViewModel { get; set; }
public MainWindow()
{
InitializeComponent();
ViewModel = new MainViewModel();
// IT WORKS IF I BRING IN THIS STATEMENT
//NoteItemControl.ItemsSource = ViewModel.notes;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
ViewModel.AddNote(new Note("note3", "Category 3"));
}
}
}
ViewModel -
namespace NotesApp.ViewModel
{
public class MainViewModel: INotifyPropertyChanged
{
ObservableCollection<Note> _notes;
public ObservableCollection<Note> notes
{
get
{ return _notes; }
set
{
_notes = value;
OnPropertyChanged("notes");
}
}
public void AddNote(Note note)
{
_notes.Add(note);
OnPropertyChanged("notes");
}
public MainViewModel ()
{
notes = new ObservableCollection<Note>();
notes.Add(new Note("note1", "Category 1"));
notes.Add(new Note("note2", "Category 2"));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged(string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs( propertyName));
}
}
}
You create a MainViewModel instance and assign it to the MainWindow's DataContext in XAML
<Window.DataContext >
<vm:MainViewModel/>
</Window.DataContext>
The bindings in your XAML use this instance as their source object, as long as you do not explicitly specify some other source. So there is no need (and it's an error) to create another instance in code behind.
Change the MainWindow's constructor like this:
public MainWindow()
{
InitializeComponent();
ViewModel = (MainViewModel)DataContext;
}
Try this :
<Window.Resources>
<vm:MainViewModel x:Key="mainVM"/>
</Window.Resources>
Now use this key as a static resource wherever you bind something like :
<ItemsControl Name="NoteItemControl" ItemsSource="{Binding notes,Source={StaticResource mainVM},Mode=TwoWay}" Background="Beige" >
If you do this, you dont need any datacontext
I'm very new to WPF, (I started yesterday) and I'm very confused about data binding. I have a View Model for a Window, which contains a widget called Foo which has its own View Model.
The widget Foo binds its Visibility TwoWay (via a BooleanToVisibilityConverter) to a bool field Visible on its FooViewModel. FooViewModel implements INotifyPropertyChanged and fires a PropertyChanged event whenever Visible is set.
In the Xaml for the Window, it creates a Foo whenever a button is clicked. The Window's view model has another boolean field which is bound TwoWay to the Visibility of its instance of a Foo View. The view model of the WIndow implements INotifyPropertyChanged and fires PropertyChanged events whenever the boolean field is modified.
What I expect this to do is whenever the window's boolean property changes, the visibility of the Foo instance will be set. When this happens I expect the View Model of Foo to be updated, since Foo's visibility binding is two way. When the Foo View Model changes its boolean field I expect the View to change its visibility. Further, I expect the Window view model to be notified that its instance of Foo is no longer visible, and hence the View model of the Window will update its own boolean field. Is this a fundamental misunderstanding?
I post the (obfuscated) code below if it helps shed light on this misunderstanding. Thanks.
Window Xaml
<Window x:Class="XXX.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:XXX.ViewModel"
xmlns:v="clr-namespace:XXX"
Title="MainWindow" Height="350" Width="525" WindowStartupLocation="CenterOwner" WindowState="Maximized">
<Window.Resources>
<vm:AppViewModel x:Key="AppViewModel"/>
<vm:TwoWayVisibilityConverter x:Key="BoolToVisibility" />
</Window.Resources>
<Grid DataContext="{Binding Source={StaticResource AppViewModel}}">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_Connection" Command="{Binding Authenticate}"/>
<MenuItem Header="_About" Command="{Binding ShowAbout}"/>
<MenuItem Header="_Logout" Command="{Binding Logout}"/>
<MenuItem Header="_Configuration" Command="{Binding Configuration}"/>
<MenuItem Header="_Info" Command="{Binding ShowInfo}"/>
</Menu>
<StackPanel>
</StackPanel>
</DockPanel>
<Border HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="White"
Padding="10"
BorderThickness="0">
<TextBlock Text="XXX"/>
</Border>
<Grid x:Name="Overlay" Panel.ZIndex="1000" DataContext="{Binding Source={StaticResource AppViewModel}}">
<Border HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Visibility="{Binding Path=Modal, Converter={StaticResource BoolToVisibility}, Mode=OneWay}"
Background="DarkGray"
Opacity=".7" />
<v:Configuration HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding Path=ConfigurationVisible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"/>
<v:Connect HorizontalAlignment="Center"
VerticalAlignment="Center"
Visibility="{Binding Path=AuthenticateVisible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"/>
</Grid>
</Grid>
Window View Model
class AppViewModel : INotifyPropertyChanged
{
[Import(typeof (IEventBus))] private IEventBus _bus;
public AppViewModel()
{
Authenticate = new ForwardCommand(obj => ShowAuthenticationView(), obj => !AuthenticateVisible);
Configuration = new ForwardCommand(obj => ShowConfigurationView(), obj => !ConfigurationVisible);
}
public bool Modal
{
get
{
return AuthenticateVisible || ConfigurationVisible;
}
}
public ICommand Authenticate { get; set; }
public bool AuthenticateVisible { get; set; }
public ICommand ShowInfo { get; set; }
public ICommand ShowAbout { get; set; }
public ICommand Logout { get; set; }
public ICommand Configuration { get; set; }
public bool ConfigurationVisible { get; set; }
private void ShowAuthenticationView()
{
AuthenticateVisible = !AuthenticateVisible;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("AuthenticateVisible"));
PropertyChanged(this, new PropertyChangedEventArgs("Modal"));
}
}
private void ShowConfigurationView()
{
ConfigurationVisible = !ConfigurationVisible;
if (null != PropertyChanged)
{
PropertyChanged(this, new PropertyChangedEventArgs("ConfigurationVisible"));
PropertyChanged(this, new PropertyChangedEventArgs("Modal"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
UserControl Xaml
<UserControl x:Class="XXX.Connect"
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:vm="clr-namespace:XXX.ViewModel"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<vm:ConnectViewModel x:Key="ViewModel"/>
<vm:TwoWayVisibilityConverter x:Key="BoolToVisibility" />
</UserControl.Resources>
<Grid Width="280"
Height="173"
DataContext="{Binding Source={StaticResource ViewModel}}"
Visibility="{Binding Path=Visible, Converter={StaticResource BoolToVisibility}, Mode=TwoWay}"
Background="White">
<Label Content="URL" Height="28" HorizontalAlignment="Left" Margin="12,12,0,0" Name="label1" VerticalAlignment="Top" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="102,12,0,0" Name="url" VerticalAlignment="Top" Width="169" Text="{Binding Path=Url, Mode=OneWayToSource}" TabIndex="0" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="102,70,0,0" Name="username" VerticalAlignment="Top" Width="171" Text="{Binding Path=Username, Mode=OneWayToSource}" TabIndex="2" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="102,41,0,0" Name="password" VerticalAlignment="Top" Width="169" Text="{Binding Path=Password, Mode=OneWayToSource}" TabIndex="1" />
<Label Content="Username" Height="28" HorizontalAlignment="Left" Margin="12,39,0,0" Name="label3" VerticalAlignment="Top" />
<Label Content="Password" Height="28" HorizontalAlignment="Left" Margin="12,68,0,0" Name="label2" VerticalAlignment="Top" />
<DockPanel HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="13">
<Button Content="OK" Height="23" HorizontalAlignment="Left" Margin="5" Name="ok" VerticalAlignment="Top" Width="75" Command="{Binding ConnectCommand}" TabIndex="3" />
<Button Content="Cancel" Height="23" HorizontalAlignment="Left" Margin="5" Name="cancel" VerticalAlignment="Top" Width="75" Command="{Binding CloseCommand}" TabIndex="4" />
</DockPanel>
</Grid>
</UserControl>
UserControl View Model
internal class ConnectViewModel : INotifyPropertyChanged
{
[Import(typeof (IEventBus))] private IEventBus _bus;
public ConnectViewModel()
{
ConnectCommand = new ForwardCommand(obj => Fire(),
obj =>
Visible && !String.IsNullOrEmpty(Url) && !String.IsNullOrEmpty(Url) &&
!String.IsNullOrEmpty(Url));
CloseCommand = new ForwardCommand(obj => Hide(), obj => Visible);
}
public ICommand ConnectCommand { get; set; }
public ICommand CloseCommand { get; set; }
public string Url { get; set; }
public string Username { get; set; }
public string Password { get; set; }
private bool _visible;
public bool Visible
{
get { return _visible; }
set { _visible = value; }
}
private void Fire()
{
_bus.Publish<SessionCreatedEvent, SessionEventHandler>(new SessionCreatedEvent(Url, Username, Password));
Hide();
}
private void Hide()
{
Visible = false;
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Binding always works on properties. So while you Raise Visible in your Hide method, you don't raise it in the actual property. But the property is what the binding engine will set. If you bind this to another dependency property it won't get notified about it.
Btw. whats a TwoWayVisibilityConverter? BooleanToVisibilityConverter is perfectly capable of handling two way bindings.
tl;dr to make two way binding work properly (and in fact even one way binding) you need to implement INotifyPropertyChanged properly which means, if the setter is called raise the property.
public bool Visible
{
get { return _visible; }
set
{
_visible = value;
if (null != PropertyChanged)
PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
}
Note: I am using Visual Studio 2012
I have the following DataTemplate (note: there is a TextBlock and Button)
<DataTemplate DataType="{x:Type ctlDefs:myButton}">
<StackPanel>
<TextBlock Text="{Binding LabelText}" Margin="10,0,10,0"/>
<Button Content="{Binding LabelText}" Margin="0,0,10,0"
Width="{Binding ButtonWidth}"
Height="35"
Command="{Binding ButtonCommand}"
Visibility="Visible"
/>
</StackPanel>
</DataTemplate>
My screen is dynamically adding controls of myButton to an Items Control
<ItemsControl ItemsSource="{Binding CommandButtons, Mode=OneWay}"
FlowDirection="LeftToRight"
DockPanel.Dock="Right"
>
The first time I render the view, the Textblock shows up for each button, but the Button itself does not. If I hide the view and then show it again, the button will appear sometimes (if the CommandButtons list does not change, if it changes, it never shows).
After rendering the view, I looked in the Output window and no binding errors.
What am I missing.
I've knocked up a quick application to work with your sample code and didn't seem to have any issues with the view. Below is what it looks like when run.
I've included my code below in case it helps. Note: I didn't add a real ICommand object for brevity and I accidentally named the MyButton class with capital M instead of small m like your example
ViewModelBase.cs
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
this.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, e);
}
}
}
public class MainWindowViewModel : ViewModelBase
{
public MainWindowViewModel()
{
this.CommandButtons = new ObservableCollection<MyButton>();
}
public ObservableCollection<MyButton> CommandButtons { get; private set; }
}
App.xaml.cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var mainvm = new MainWindowViewModel();
var window = new MainWindow
{
DataContext = mainvm
};
window.Show();
mainvm.CommandButtons.Add(new MyButton { ButtonWidth = 50, LabelText = "Button 1" });
mainvm.CommandButtons.Add(new MyButton { ButtonWidth = 50, LabelText = "Button 2" });
mainvm.CommandButtons.Add(new MyButton { ButtonWidth = 50, LabelText = "Button 3" });
}
}
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ctlDefs="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate DataType="{x:Type ctlDefs:MyButton}">
<StackPanel>
<TextBlock Text="{Binding LabelText}" Margin="10,0,10,0"/>
<Button Content="{Binding LabelText}" Margin="0,0,10,0"
Width="{Binding ButtonWidth}"
Height="35"
Command="{Binding ButtonCommand}"
Visibility="Visible"
/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ItemsControl ItemsSource="{Binding CommandButtons, Mode=OneWay}"
FlowDirection="LeftToRight"
DockPanel.Dock="Right"
/>
</Grid>
MyButton.cs
public class MyButton : ViewModelBase
{
private string _labelText;
public string LabelText
{
get { return this._labelText; }
set { this._labelText = value; this.OnPropertyChanged("LabelText"); }
}
private double _buttonWidth;
public double ButtonWidth
{
get { return _buttonWidth; }
set { _buttonWidth = value; this.OnPropertyChanged("ButtonWidth"); }
}
private ICommand _buttonCommand;
public ICommand ButtonCommand
{
get { return _buttonCommand; }
set { _buttonCommand = value; this.OnPropertyChanged("ButtonCommand"); }
}
}
How do you make a Button call ICommand.CanExecute when the command parameter is changed?
This is my current XAML.
<Button Content="Delete" Command="{Binding DeleteItemCommand}" CommandParameter="{Binding SelectedItem, ElementName=DaGrid}" />
EDIT It appears this is only an issue in WPF.
I'm not sure what you're doing wrong, but here is an example of a Button being controlled both by a BindingParameter and a CanExecute Flag. Perhaps your binding parameter isn't a DependencyProperty, and therefore, when it changes the Button isn't being notified.
<UserControl x:Class="SilverlightICommandTest.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:ct="clr-namespace:SilverlightICommandTest"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.Resources>
<ct:TestModel x:Key="Model" />
</UserControl.Resources>
<StackPanel x:Name="LayoutRoot" Orientation="Vertical" Background="White" DataContext="{StaticResource Model}">
<CheckBox Content="Enable" IsChecked="{Binding TestCmd.CanDoCommand, Mode=TwoWay}" />
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ElementName=testSlider, Path=Value}" Width="40" Grid.Column="0" />
<Slider Name="testSlider" Minimum="0" Maximum="100" SmallChange="1" Grid.Column="1" />
</Grid>
<Button Command="{Binding TestCmd}" CommandParameter="{Binding ElementName=testSlider, Path=Value}" Content="Do Something" />
</StackPanel>
</UserControl>
And the code file:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SilverlightICommandTest
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
}
public class TestModel : DependencyObject
{
TestCommand _testCmd = new TestCommand();
public TestCommand TestCmd { get { return _testCmd; } }
public TestModel()
{
}
}
public class TestCommand : DependencyObject, ICommand
{
public static readonly DependencyProperty CanDoCommandProperty = DependencyProperty.Register("CanDoCommand", typeof(Boolean), typeof(TestCommand), new PropertyMetadata(false, new PropertyChangedCallback(CanDoCommandChanged)));
public Boolean CanDoCommand
{
get { return (Boolean)GetValue(CanDoCommandProperty); }
set { SetValue(CanDoCommandProperty, value); }
}
public event EventHandler CanExecuteChanged;
public TestCommand()
{
}
public Boolean CanExecute(Object parameter)
{
return this.CanDoCommand && (((Int32)(Double)parameter) % 2 == 0);
}
public void Execute(Object parameter)
{
MessageBox.Show("Oh Hai!");
}
private void OnCanDoCommandChanged(DependencyPropertyChangedEventArgs args)
{
if (this.CanExecuteChanged != null)
{
this.CanExecuteChanged(this, new EventArgs());
}
}
private static void CanDoCommandChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
((TestCommand)sender).OnCanDoCommandChanged(args);
}
}
}
In the future I recommend doing a little more research on the pattern first (http://www.silverlightshow.net/items/Model-View-ViewModel-in-Silverlight.aspx), and if you still can't figure it out, post more of your source code.
Strange. Normally OnCommandParameterChanged calls UpdateCanExecute (both internal methods). Does the Binding to CommandParameter work as expected?
You need to call CommandManager.InvalidateRequerySuggested to re-evaluate CanExecute. Note that it will re-evaluate it for all commands, not just the one your want...
This code works in WPF but not in Silverlight.
Are there any workarounds in Silverlight to enable to me to bind a slider value to element heights? What are the limitations of Silverlight here?
ANSWER:
Thanks Peter for solving this, for others: here is the solution with online demo and downloadable code.
<UserControl x:Class="Second12.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:basics="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
Width="300" Height="200">
<Grid Background="Tan">
<StackPanel>
<Canvas>
<Border Background="#2200ffff"
Canvas.Left="40"
Canvas.Top="30"
CornerRadius="5"
BorderBrush="Brown"
BorderThickness="1">
<Rectangle
Height="{Binding ElementName=theSlider, Path=Value}"
Width="50"/>
</Border>
</Canvas>
</StackPanel>
<Slider Name="theSlider" HorizontalAlignment="Left" Width="200" Cursor="Hand"/>
</Grid>
</UserControl>
ElementName binding is not currently supported in Silverlight 2. This MSDN documentation article is quite succinct on the specifics of the WPF functionality that Silverlight does and doesn't currently provide.
In the interim you have to use some sort of intermediary property, here is a full example:
Page.xaml:
<UserControl x:Class="SilverlightApplication1.Page"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<StackPanel x:Name="LayoutRoot" Background="AliceBlue">
<Slider x:Name="Slider1" Minimum="10" SmallChange="10"
LargeChange="20" Maximum="100"
Value="{Binding HelperValue, Mode=TwoWay}" Cursor="Hand"/>
<Rectangle x:Name="Rectangle1" Fill="Red"
Height="{Binding HelperValue, Mode=OneWay}" Width="100" />
</StackPanel>
</UserControl>
Page.xaml.cs:
using System;
using System.ComponentModel;
using System.Windows.Controls;
namespace SilverlightApplication1
{
public partial class Page : UserControl
{
BindingConduit<double> sliderToRect = new BindingConduit<double>(50.0);
public Page()
{
InitializeComponent();
LayoutRoot.DataContext = sliderToRect;
}
}
public class BindingConduit<T> : INotifyPropertyChanged where T : struct
{
private T helperValue;
public T HelperValue
{
get { return this.helperValue; }
set
{
if ((this.helperValue.Equals(value) != true))
{
this.helperValue = value;
this.RaisePropertyChanged("HelperValue");
}
}
}
public BindingConduit(T defaultValue)
{
HelperValue = defaultValue;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
The Value property of the Slider is bound to HelperValue and set to TwoWay, the Rectangle's Height is bound to the same property but only requires OneWay.
The HelperValue property is being read from the instance of the BindingConduit<T> class created in Page called sliderToRect. This instance is set as the DataContext of the parent StackPanel control. Any change in Slider1's Value property is reflected in the Height of Rectangle1 because the HelperValue property raises the PropertyChanged event.
BindingConduit<T> is a custom class I've created that implements INotifyPropertyChanged and also lets you specify a default value for HelperValue (i.e. the inital Value of Slider1 and the Height of Rectangle1 (50.0) in this case).