I have a ContextMenu with a MenuItem in it:
<Grid>
<Button Content="{Binding Test}">
<Button.ContextMenu>
<ContextMenu>
<StackPanel>
<MenuItem Header="{Binding Test}"/>
</StackPanel>
</ContextMenu>
</Button.ContextMenu>
</Button>
</Grid>
The Test property looks like the following:
private Random rand;
public string Test
{
get
{
return "Test " + this.rand.Next(50);
}
}
When I right click the button, I have, for instance "Test 41". Next times I open the menu I have the same value. Is there a way to force the Menu to evaluate the binding each time ? (and then having "Test 3", "Test 45", "Test 65"...
Here is a hack i use in the same situation:
Name your context menu and create your own RoutedCommand, i use these for all buttons and menus as they have a CanExecute method which enables or disables the control and an Execute method that gets called to do the work. every time a context menu opens the CanExecute method gets called. that means you can do custom processing to see if it should be enabled, or you can change the contents of the menu, good for changing menu's when saving different things. we use it to say, Save xyx.. when the user is editing an xyx.
anyway if the menu is named you can modify its content on the CanExecute. (if the command originates on the menu you will have it as the sender of the event CanExecute anyway, but sometimes i like to scope them higher as you can assign keyboard shortcuts to them which can be executed from anywhere they are scoped.)
Your Test property needs to inform other components whenever its value changes, e.g. by implementing the INotifyPropertyChanged interface in the containing class like this:
class Window1 : Window, INotifyPropertyChanged {
...
private string m_Test;
public string Test {
get {
return m_Test;
}
set {
m_Test = value;
OnPropertyChanged("Test");
}
}
}
You can then modify the value of Test from anywhere by using the property (Test = "newValue";) and the changes will be reflected on the UI.
If you really need to change the value of the property when the ContextMenu is shown, use the Opend event of the ContextMenu:
Xaml:
<ContextMenu Opened="UpdateTest">
<MenuItem Header="{Binding Test}" />
</ContextMenu>
Code-behind:
private void UpdateTest(object sender, RoutedEventArgs e) {
// just assign a new value to the property,
// UI will be notified automatically
Test = "Test " + this.rand.Next(50);
}
Related
This question already has answers here:
WPF CommandParameter is NULL first time CanExecute is called
(16 answers)
Closed 6 years ago.
Not sure if this is something I am not understanding about WPF or about Catel, but I have a treeview with 3 datatemplates for different node types. 2 of the node types can be bound to a delete button. The binding of the button command binds to the viewmodel of the parent control (rather than to the node itself) and a command parameter of the node where the button was clicked is passed. I am providing a small snippet of one of the data templates (the whole thing is too large to enter here):
<Grid Margin="10" x:Name="CriteriaGrid">
<TreeView ItemsSource="{Binding Criteria}" >
<DataTemplate DataType="{x:Type self:Leaf}">
<Button Command="{Binding Source={x:Reference CriteriaGrid}, Path=DataContext.DeleteLeaf}"
CommandParameter="{Binding}">X</Button>
</DataTemplate>
</TreeView>
</Grid>
ViewModel (again just a small extract):
public class ManageCriteriaViewModel : ViewModelBase
{
public ManageCriteriaViewModel()
{
DeleteLeaf = new Command<Leaf>(OnDeleteLeaf, CanDeleteLeaf);
}
private bool CanDeleteLeaf(Leaf leafNode)
{
return (leafNode?.Parent as Group) != null;
}
private void OnDeleteLeaf(Leaf leafNode)
{
// Some code
}
public Command<Leaf> DeleteLeaf { get; private set; }
}
The problem is that when the Tree is initially being constructed, the command parameter is always null, and my CanExecute test returns false if the parameter is null. So when my tree initially displays, all my buttons are disabled.
However, if I click any of the buttons, all of them get re-evaluated and become enabled because now the command parameter is being passed correctly.
I have tried adding:
protected override Task InitializeAsync()
{
CommandManager.InvalidateRequerySuggested();
ViewModelCommandManager.InvalidateCommands(true);
return base.InitializeAsync();
}
In an attempt to re-evaluate all the commands after the UI is loaded but this does not seem to work. What am I missing here?
Try switching the order of the command parameter and command object. The reason is that the CommandParameter is not bindable, and does not raise any change notifications. Therefore, the command is not being re-evaluated after "updating" (it's still the initial binding process).
If that doesn't work, something like this could:
protected override async Task InitializeAsync()
{
await base.InitializeAsync();
_dispatcherService.BeginInvoke(() => ViewModelCommandManager.InvalidateCommands(true););
}
I have a value that can change that doesn't raise a change event and the menu item bound to the value doesn't correctly reflect the state when the menu item is opened. I'd like to update this binding when the menu opens. How do I do this?
Can I have a menu item that just polls it's bindings each time the menu is opened? In this case the IsCommEnabled property:
<MenuItem Header="{Binding EnableComm}"
Command="{Binding Root.ToggleCommunications}"
IsChecked="{Binding Authorization.IsCommEnabled, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"/>
--
public bool IsCommEnabled {
get { return _communications.IsCommEnabled; }
}
You can subscribe to the SubmenuOpened event, and manually update the binding:
void MenuItem_SubmenuOpened(object sender, RoutedEventArgs e)
{
((MenuItem)sender).GetBindingExpression(MenuItem.IsCheckedProperty).UpdateTarget();
}
Please note that the above applies to the Parent item being opened, so you may need to wrangle it a bit to ensure that it is the right item that is getting updated. You can use the Items collection on the MenuItem to dig deeper.
You have to raise NotifyPropertyChanged if you want to push the IsCommEnabled back to the bound Dependency Property
To learn WP7 i'm making a simple soundboard application.
This is my code.
Please ask if i accidentally left something out in my tries to keep things simple.
MainViewModel contains this collection
public ObservableCollection<SoundViewModel> Sounds { get; private set; }
The soundViewModel contains these properties, they are both notifying any property changes
public string FileName
public string Name
Xaml / View contains a listbox bound to the Sounds collection on the viewmodel
<ListBox x:Name="FirstListBox" Margin="0,0,-12,0" ItemsSource="{Binding Sounds}">
<ListBox.ItemTemplate>
<DataTemplate>
<Button Content="Play" CommandParameter="{Binding FileName}" Command="{Binding DataContext.PlaySound, ElementName=FirstListBox}" />
<es:Arc x:Name="arc" ..bla bla attributes. />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
When i click the button, the parameter / filename is used to located the file and invoke the following method.
This method is enclosed in a RelayCommand and bound to the button in the datatemplate
private void PlaySound( string filename )
{
Stream stream = TitleContainer.OpenStream(filename + ".wav");
SoundEffect effect = SoundEffect.FromStream(stream);
PlayAnimation message = new PlayAnimation(effect.Duration);
Messenger.Default.Send<PlayAnimation>(message);
FrameworkDispatcher.Update();
effect.Play();
}
This works great.
Now i want a small animation playing while the sound is playing.
I'm thinking that i will just pass the length of the soundclip to the animation and then start it.
The messenger class sends a message (in the PlaySound method, above), and this code wires it up to a method
------ View / Xaml Constructor ------
Messenger.Default.Register<PlayAnimation>(this, ( action ) => PlayAnimation(action));
------ method PlayAnimation below ------
private void PlayAnimation(PlayAnimation parameter)
{
//Magic code starting the animation in the datatemplate of the listbox..
}
But i'm not exactly sure how i can start the animation.
The storyboard is a resource to the listbox, and the targetElement is the Arc element.
So the animation is simply the arc element's startAngle, and is just there to illustrate that a sound i currently playing, and how long the sound duration is.
Somehow i need to get a hold of the storyboard, but since the targetElement is inside a data-template, how will i know how to play the correct animation. That is if i'm even able to get reference to the storyboard?
Thanks in advance!
Please ask if there is anything
First off define your animation in your data template.
Next instead of binding the CommandParameter to "FileName" bind it to the object in your data template where the animation is defined. Normally your data template will contain a root layout panel where the animation is defined as a resource. Make sure that has an x:Name and bind your CommandParameter to that. So if your root layout container were called "grid" bind the CommandParameter to it like this.
CommandParameter="{Binding ElementName=grid, Mode=OneWay}"
Now in the handler for your relay command change the parameter to reflect that you are now passing in a FrameworkElement rather than your file name string. Also change your code to extract the Storyboard and use the DataContext to get back to your viewmodel to get the filename of the sound.
private void PlaySound( FrameworkElement obj )
{
var animation = obj.Resources["MyAnimation"] as Storyboard;
animation.Begin();
var selected = obj.DataContext as SoundViewModel;
var filename = selected.FileName;
Stream stream = TitleContainer.OpenStream(filename + ".wav");
SoundEffect effect = SoundEffect.FromStream(stream);
PlayAnimation message = new PlayAnimation(effect.Duration);
Messenger.Default.Send<PlayAnimation>(message);
FrameworkDispatcher.Update();
effect.Play();
}
I'm using Silverlight, but I'd be interested in a WPF answer as well
I have a list that is databound to an linked list of “Favorites”. Each favorite contains a name and a phone number.
The list is bound to a DataTemplate that describes the graphical aspects. In the this template is a button – Dial. When you click on that button I want the Dial() method of the Favorite to be called. Right now the Dial method of the page/window is called.
If this is not possible is there a way I can get the Favorite to somehow be attached to the Button? such that I know which Favorite was associated with the button press?
the below XAML does not work, Text="{Binding Name}" works great as it binds to the Name property on the Favorite, but Click="{Binding Dial}" does not call Dial() on the Favorite.
<DataTemplate x:Key="DataTemplate1">
<StackPanel d:DesignWidth="633" Orientation="Horizontal" Height="93">
<Button x:Name="DialButton" Content="Edit" Click="{Binding Dial}"/>
<TextBlock x:Name="TextBlock" TextWrapping="Wrap" Text="{Binding Name}" FontSize="64" Height="Auto" FontFamily="Segoe WP SemiLight"/>
</StackPanel>
</DataTemplate>
So it should go:
<Button CommandParameter="{Binding}" Command="{Binding Dial}"/>
Then you will receive the data object as the command parameter. In this scenario you must provide a Property that is called Dial and returns an ICommand-implementation. If the property is not available on your data-object but on the main class (code-behind), you must look for it within the binding, use for this the RelativeSource keyword.
Another way is to make a click handler. In the click handler you can cast the sender to a Button (or FrameworkElement) and then get the data object from the DataContext. I assume you tried to create such a solution.
private void Button_Click(object sender, RoutedEventArgs e) {
Button btn = (Button)sender;
MyObject obj = btn.DataContext as MyObject;
if(null != obj){
obj.Dial();
// or Dial(obj);
}
}
The markup must be as follows:
<Button x:Name="DialButton" Content="Edit" Click="Button_Click"/>
The main difference is, that I removed the binding from the Click-Event and registered an event-handler.
A third solution would be, to register a handler in the code behind for the Button.ClickEvent. The principle is similiar as in the second example.
I don't know silverlight very well. Perhaps there are the things a little bit other.
HappyClicker's first solution is the best one for most purposes, since it supports good design patterns such as MVVM.
There is another simple way to get the same result using an attached property, so you can write:
<Button Content="Edit" my:RouteToContext.Click="Edit" />
and the Edit() method will be called on the button's DataContext.
Here is how the RouteToContext class might be implemented:
public class RouteToContext : DependencyObject
{
public static string GetClick(FrameworkElement element) { return (string)element.GetValue(ClickProperty); }
public static void SetClick(FrameworkElement element, string value) { element.SetValue(ClickProperty, value); }
public static DependencyProperty ClickProperty = ConstructEventProperty("Click");
// Additional proprties can be defined here
private static DependencyProperty ConstructEventProperty(string propertyName)
{
return DependencyProperty.RegisterAttached(
propertyName, typeof(string), typeof(RouteToContext),
new PropertyMetadata
{
PropertyChangedCallback = (obj, propertyChangeArgs) =>
obj.GetType().GetEvent(propertyName)
.AddEventHandler(obj, new RoutedEventHandler((sender, eventArgs) =>
((FrameworkElement)sender).DataContext
.GetType().GetMethod((string)propertyChangeArgs.NewValue)
.Invoke(((FrameworkElement)sender).DataContext,
new object[] { sender, eventArgs }
)
))
}
);
}
}
How it works: When the RouteToContext.Click attached property is set, Type.GetEvent() is used to find the event named "Click", and an event handler is added to it. This event handler uses Type.GetMethod() to find the specified method on the DataContext, then invokes the method on the DataContext, passing the same sender and eventArgs it received.
I have implemented a custom IComand class for one of my buttons. The button is placed in a page 'MyPage.xaml' but its custom ICommand class is placed in another class, not in the MyPage code behind. Then from XAML I want to bind the button with its custom command class and then I do:
MyPage.xaml:
<Page ...>
<Page.CommandBindings>
<CommandBinding Command="RemoveAllCommand"
CanExecute="CanExecute"
Executed="Execute" />
</Page.CommandBindings>
<Page.InputBindings>
<MouseBinding Command="RemoveAllCommand" MouseAction="LeftClick" />
</Page.InputBindings>
<...>
<Button x:Name="MyButton" Command="RemoveAllCommand" .../>
<...>
</Page>
and the custom command button class:
// Here I derive from MyPage class because I want to access some objects from
// Execute method
public class RemoveAllCommand : MyPage, ICommand
{
public void Execute(Object parameter)
{
<...>
}
public bool CanExecute(Object parameter)
{
<...>
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
My problem is how to say MyPage.xaml that Execute and CanExecute methods for the button is in another class and not the code behind where is placed the button. How to say these methods are in RemoveAllCommand Class in XAML page.
Also I want to fire this command when click mouse event is produced in the button so I do an input binding, is it correct?
Thanks
Since you have an ICommand, you can bind it to the Button through the Command property, and the button will use it, i.e. it will call CanExecute to enable/disable itself and the Execute method when the button is pressed. There is no need for any other input binding.
Now the problem is that the button has to find the command. A simple solution is to put an instance of the command in the DataContext of the button (or of its parents).
If the DataContext has a property called RemoveAll of type RemoveAllCommand, you can simply change your XAML button to:
<Button Command="{Binding RemoveAll}" .. />
and remove the CommandBinding and InputBinding
oK, Thanks, tomorrow I'll try it.
Now to avoid problems I have moved all in RemoveAllCommandClass Class to the code behind of MyPage and I have done some modifications.
1.- I added this to MyPage.xaml:
xmlns:local="clr-namespace:GParts"
then I have done:
<Page.CommandBindings>
<CommandBinding Command="{x:Static local:Pages.MyPage._routedCommand}"
Executed="Execute"
CanExecute="CanExecute"/>
</Page.CommandBindings>
<Button Command="{x:Static local:Pages.MyPage._routedCommand}" .../>
And all is ok and works. When I press button it executes background worker (bw) that is called in Execute method. bw is into another class. In background worker class I have a variable (isRunning) that indicates if the bw is executing. Before executing DoWork event I set it to true and when bw completes, at RunWorkerCompleted, I set it to false. So from CanExecute I check isRunning in bw class and I set to true e.canExecute if isRunning is false, and e.canExecute to false if isRunning is true. So the button is disabled by WPF automatically when bw is running but when bw finishes the button continues disabled and not returns to enabled until I press it again. Why is WPF not updating the button state to enabled when bw finishes until I press again the button?
Thanks.