Commands not working / firing on events built in my customized UserControl - c#

I'm working on this (MVVM) Windows 8.1 metro app where I'm using a UserControl that I built separately, this UserControl has its own events that I built for it.
My problem is: when I try in the global app to use commands on those they never fires, although the events work!
The UserControl and its events :
public delegate void PlayClickedEventHandler(object sender, RoutedEventArgs e);
...
public event PlayClickedEventHandler PlayClicked;
The PlayClicked event is fired when a button Inside the UserControl is clicked
private void PlayButton_OnClick(object sender, RoutedEventArgs e)
{
if (PlayClicked != null)
PlayClicked(this, e);
}
The Global Windows 8.1 MVVM App :
The View :
<mediaPlayerControl:PlayerControl x:Name="UserControl">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="PlayClicked">
<core:InvokeCommandAction Command="{Binding OnPlayClicked, Mode=TwoWay}"/>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</mediaPlayerControl:PlayerControl>
The ViewModel :
public RelayCommand OnPlayClicked { get; set; }
OnPlayClicked = new RelayCommand(() =>
{
//Stuff to do here that seem not to be done
});
Am I missing something ??

I guess this could explain it (from the docs on EventTriggerBehavior)
The following events are supported:
(list of 11 events)
Consider implementing a custom behavior to respond to other events.
Why not use an EventTrigger instead? (Also, note that the "OnPlayClicked" binding should really be one way, although this shouldn't make a difference.)
<mediaPlayerControl:PlayerControl x:Name="UserControl">
<interactivity:Interaction.Triggers>
<uixaml:EventTrigger EventName="PlayClicked">
<core:InvokeCommandAction Command="{Binding OnPlayClicked}"/>
</uixaml:EventTrigger>
</interactivity:Interaction.Triggers>
</mediaPlayerControl:PlayerControl>

Related

Binding of changing click event

Good day. I'll try to make it shorter as possible. My XAML:
<StackPanel Grid.Row="1" Grid.Column="0" x:Name="stackActions">
<Button x:Name="btnAction_1" Margin="5" Content="Start work" Click="btnAction_1_Click"/>
<Button x:Name="btnAction_2" Margin="5" Content="" Visibility="Collapsed"/>
<Button x:Name="btnAction_3" Margin="5" Content="" Visibility="Collapsed"/>
<Button x:Name="btnAction_4" Margin="5" Content="" Visibility="Collapsed"/>
<Button x:Name="btnAction_5" Margin="5" Content="" Visibility="Collapsed"/>
</StackPanel>
My C#:
public partial class MainWindow : Window
{
// Method for collapsing all buttons
public void HideAllButtons()
{
stackActions.Children.OfType<Button>().ToList().ForEach(button => button.Visibility = Visibility.Collapsed);
}
public MainWindow()
{
InitializeComponent();
}
private void btnAction_1_Click(object sender, RoutedEventArgs e)
{
HideAllButtons();
btnAction_2.Visibility = Visibility.Visible;
btnAction_3.Visibility = Visibility.Visible;
btnAction_2.Content = "Surveys";
btnAction_3.Content = "ClearAll";
btnAction_2.Click += SeeSurveys;
btnAction_3.Click += ClearAll;
}
private void SeeSurveys(object sender, RoutedEventArgs e )
{
btnAction_2.Click -= SeeSurveys;
btnAction_3.Click -= ClearAll;
btnAction_2.Content = "Draft";
btnAction_3.Content = "OnHire";
btnAction_4.Visibility = Visibility.Visible;
btnAction_4.Content = "Condition";
btnAction_5.Visibility = Visibility.Visible;
btnAction_5.Content = "Back";
btnAction_5.Click += btnAction_1_Click;
}
private void ClearAll (object sender, RoutedEventArgs e)
{
HideAllButtons();
btnAction_1.Visibility = Visibility.Visible;
}
}
Actually this is works as i want, but looking like mess with much Click += and Click -= events. I understand that making more massive code confused myseld very soon. I suppose there are any options to bind click event for each button in XAML and change the event itself in C# or reduce the quantity of Visibility checks.
The main idea is changing click events and rewriting buttons content depending of what button was clicked.
So any advice is welcome to make this code more clear and short.
Like many wpf developers and the vast majority of commercial wpf teams, I use MVVM.
With that I would be thinking in terms of templating buttons out from viewmodels that had commands.
Rather than having many buttons where I switched the event handler round, I'd have lists of commands. These could be in a model which was a hierarchical form and a layer translated or picked from that if it suited your real world requirement best.
Or
A series of objects something like:
public class CommandListViewModel
{
public int Id { get; set; }
public ObservableCollection<CommandViewModel> Commands { get; set; }
}
public class CommandViewModel
{
public string Name { get; set; }
public DelegateCommand<MainWindowViewModel> Command { get; set; }
}
These are just sketched out to give an idea of what I have in mind. You would have inotifypropertychanged implemented in these viewmodels and probably want to raise change notification from the property setters.
But the idea is each level is represented by a class commandlistviewmodel which has an id and a bunch of commands with the "name". The name is displayed in a button which has that command.
Each command gets a reference to mainwindowviewmodel passed into it. So it can access properties on that. One of which would be a list or instance of commandlistviewmodel.
Hence any given command could go find a list of commands it needs to "substitute" or some other property in mainwindowviewmodel it should manipulate.
You can create delegatecommand ( or relaycommand ) using lambdas.
Rather or as well as passing in an instance of mainwindowviewmodel these could potentially capture variables from wherever they are constructed. You could make that a mediator or event aggregator which abstracted UI services.
Essentially though.
These commands do what your click handlers do.
These lists of commands are your various switchable levels.
You work with these and bind them to the UI to template into a bunch of buttons.
The way you do that templating is in an itemscontrol. Or a listbox if you wanted to have a built in slider and maybe indicate which one was clicked last using selecteditem.
<ItemsControl ItemsSource="{Binding Commands}"
>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type local:CommandViewModel}">
<Button Content="{Binding Name}"
Command="{Binding Command}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
With the above a viewmodel which is the datacontext of this itemscontrol ( usually also of the entire window ) would have a public property which was an observablecollection of CommandViewModel. The items or the entire collection are switched out depending on the "level" chosen. You click one command which chooses Surveys and a list of commandviewmodel appropriate for Surveys are shown. One of those looks like it'd be a "Back" command which switched back to the previous list. And so on.
You could also have a property in a commandviewmodel which binds to Visibility so you could collapse a button. I'm not sure you'd need that with this pattern though. It will automatically cope with a "level" which has 3, 5, 10 or whatever number of commands.
You will have a fair bit of reading to do before you would be in a position to switch from your current pattern to mvvm. Delegatecommand is just one of your options, it's from Prism and there's a prism.mvvm nuget you could consider. Or you might prefer mvvmlight which has relaycommand. You'll want to use one of these or some other such framework to save you writing your own icommand implementation.
I hope that's sufficient to at least get you started.

How to run a method every time a TabItem is selected, in an MVVM application using Prism

I have been trying to implement this for a while and haven't been able to do it so far, despite having the feeling that this should be something easy.
The difficulty comes from the fact that I have implemented a WPF application using the MVVM pattern. Now, this is my first attempt at both the pattern and the framework, so it is almost guaranteed that I have made mistakes while trying to follow the MVVM guidelines.
My implementation
I have three Views with their respective ViewModels (wired using Prism's AutoWireViewModel method). The MainView has a TabControl with two TabItems, each of witch contains a Frame container with the Source set to one of the other two Views. The following code is an excerpt of the MainView:
<TabControl Grid.Row="1" Grid.Column="1">
<TabItem Header="Test">
<!--TestView-->
<Frame Source="View1.xaml"/>
</TabItem>
<TabItem Header="Results">
<!--ResultsView-->
<Frame Source="View2.xaml"/>
</TabItem>
</TabControl>
My problem
Every time that someone changes to a specific TabItem, I would like to run a method that updates one of the WPF controls included in that View. The method is already implemented and bound to a Button, but ideally, no button should be necessary, I would like to have some kind of Event to make this happen.
I appreciate all the help in advance.
You could for example handle the Loaded event of the Page to either call a method or invoke a command of the view model once the view has been loaded initially:
public partial class View2 : Page
{
public View2()
{
InitializeComponent();
Loaded += View2_Loaded;
}
private void View2_Loaded(object sender, RoutedEventArgs e)
{
var viewModel = DataContext as ViewModel2;
if (viewModel != null)
viewModel.YourCommand.Execute(null);
Loaded -= View2_Loaded;
}
}
The other option would be handle this in the MainViewModel. You bind the SelectedItem property of the TabControl to a property of the MainViewModel and set this property to an instance of either ViewModel2 or ViewModel2, depending on what kind of view you want to display.
You could then call any method or invoked any command you want on these. But this is another story and then you shouldn't hardcode the TabItems in the view and use Frame elements to display Pages. Please take a look here for an example:
Selecting TabItem in TabControl from ViewModel
Okay, so What I have done is Create a Custom Tab Control. I will write out step by step instructions for this, and then you can add edit to it.
Right click on your solution select add new project
Search For Custom Control Library
High Light the name of the class that comes up, and right click rename it to what ever you want I named it MyTabControl.
Add Prism.Wpf to the new project
Add a reference to the new project to where ever your going to need it. I needed to add to just the main application, but if you have a separate project that only has views then you will need to add it to that too.
Inherit your Custom Control From TabControl Like:
public class MyTabControl : TabControl
You will notice that there is a Themes folder in the project you will need to open the Generic.xaml and edit it. it should look like:
TargetType="{x:Type local:MyTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" for some reason this will not let me show the style tags but they will need to be in there as well
Please review this code I got this from Add A Command To Custom Control
public class MyTabControl : TabControl
{
static MyTabControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTabControl), new FrameworkPropertyMetadata(typeof(MyTabControl)));
}
public static readonly DependencyProperty TabChangedCommandProperty = DependencyProperty.Register(
"TabChangedCommand", typeof(ICommand), typeof(MyTabControl),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandCallBack)));
private static void CommandCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myTabControl = (MyTabControl)d;
myTabControl.HookupCommands((ICommand) e.OldValue, (ICommand) e.NewValue);
}
private void HookupCommands(ICommand oldValue, ICommand newValue)
{
if (oldValue != null)
{
RemoveCommand(oldValue, oldValue);
}
AddCommand(oldValue, oldValue);
}
private void AddCommand(ICommand oldValue, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
var canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.TabChangedCommand != null)
{
if (TabChangedCommand.CanExecute(null))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
public ICommand TabChangedCommand
{
get { return (ICommand) GetValue(TabChangedCommandProperty); }
set { SetValue(TabChangedCommandProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (TabChangedCommand != null)
{
TabChangedCommand.Execute(null);
}
}
}
you will need to add the name space in your window or usercontrol like:
xmlns:wpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"
and here is your control:
<wpfCustomControlLibrary1:MyTabControl TabChangedCommand="{Binding TabChangedCommand}">
<TabItem Header="View A"></TabItem>
<TabItem Header="View B"></TabItem>
</wpfCustomControlLibrary1:MyTabControl>
This is how I'd approach this sort of requirement:
View:
<Window.DataContext>
<local:MainWIndowViewModel/>
</Window.DataContext>
<Grid>
<TabControl Name="tc" ItemsSource="{Binding vms}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:uc1vm}">
<local:UserControl1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:uc2vm}">
<local:UserControl2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabHeading}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
When it has a uc1vm it will be templated into usercontrol1 in the view.
I'm binding to a collection of viewmodels which all implement an interface so I know for sure I can cast to that and call a method.
Main viewmodel for window:
private IDoSomething selectedVM;
public IDoSomething SelectedVM
{
get { return selectedVM; }
set
{
selectedVM = value;
selectedVM.doit();
RaisePropertyChanged();
}
}
public ObservableCollection<IDoSomething> vms { get; set; } = new ObservableCollection<IDoSomething>
{ new uc1vm(),
new uc2vm()
};
public MainWIndowViewModel()
{
}
When a tab is selected, the setter for selected item will be passed the new value. Cast that and call the method.
My interface is very simple, since this is just illustrative:
public interface IDoSomething
{
void doit();
}
An example viewmodel, which is again just illustrative and doesn't do much:
public class uc1vm : IDoSomething
{
public string TabHeading { get; set; } = "Uc1";
public void doit()
{
// Your code goes here
}
}
I appreciate all of your input, but I found an alternative solution. Given the information given by #mm8, I took advantage of the Loaded event but in a way that does not require any code in the code behind.
My solution
In the View which I would like to give this ability to execute a method every time the user selects the TabItem that contains it, I added the following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
And then simply implemented a DelegateCommand called OnLoadedCommand in the View's respective ViewModel. Inside that command I call my desired method.
Please comment if you spot anything wrong with this approach! I chose to try this since it required the least amount of changes to my code, but I may be missing some vital information regarding problems the solution may cause.

c# mvvm proper event disposal

I have implemented MVVM pattern in my WPF windows. I have a subwindow which I call the following way (from another ViewModel):
cmd_Show = new DelegateCommand(
(sender) =>
{
frm_Strediska _window = new frm_Strediska();
frm_StrediskaViewModel vm = new frm_StrediskaViewModel(ZakladneStrediska, _window);
_window.DataContext = vm;
_window.Owner = App.Current.MainWindow;
_window.ShowDialog();
});
Here under frm_StrediskaViewModel I hook to a Window.Closing event in a following way:
public frm_StrediskaViewModel(ObservableCollection<DefaultStrediska> _Strediska, frm_Strediska _Window)
{
Window = _Window;
Strediska = _Strediska;
InitializeCommands();
Window.Closing += Window_Closing;
}
The thing I am not sure about is, that when I close this SubWindow, if my Window.Closing event unhooks automatically, or I have to override Dispose() event on the ViewModel (that's what I am doing now):
protected override void Dispose(bool disposing)
{
Window.Closing -= Window_Closing;
this.Dispose();
}
Is this a good approach, or is it completely unnecessary?
There is a way to do this with neat bindings, but it requires the Blend SDK. You need the System.Windows.Interactivity.dll for this. The first thing required is to add the namespace declaration:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
With this import you gain the possibility to bind commands to events like so:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<i:InvokeCommandAction Command="{Binding CloseCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Adding this to your window will cause the command bound with CloseCommand to be executed once the Closing-Event is raised by the window.
So you don't need to do any binding to the event yourself. You can just create the required property in your ViewModel that returns the command. This command has to do what ever is required during the closing. So basically what you got in your event handler currently.
public readonly ICommand CloseCommand
{
get { return /* Your closing command here */; }
}
I hope that helps.

How to add event handler in MVVM in Windows Phone 8?

All
I have a issue. Now I'm using MVVM framework to develop Windows Phone 8 app. I just want to when I press the button, then begin to record something, when release the button, stop recording, I used InvokeCommandAction to bind the command in ViewModel, this is the code as follow
Xaml:
<Button x:Name="BtnRecord" Height="50" Width="180" Background="#D43637" Content="Record" Margin="20,0,0,0" Style="{StaticResource BasicButtonStyle}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<i:InvokeCommandAction Command="{Binding StartRecordCommand}"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeftButtonUp">
<i:InvokeCommandAction Command="{Binding EndRecordCommand}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
ModelView:
public ICommand StartRecordCommand
{
get
{
return new DelegateCommand(StartRecord);
}
}
public ICommand EndRecordCommand
{
get
{
return new DelegateCommand(EndRecord);
}
}
private void StartRecord(object parameter){}
private void EndRecord(object parameter){}
When I debug the app, I found it didn't fire the neither the MouseLeftButtonDown nor MouseLeftButtonUp events, so I register the two event handler as follow:
BtnRecord.AddHandler(UIElement.MouseLeftButtonDownEvent, new MouseButtonEventHandler(Button_MouseLeftButtonDown), true);
BtnRecord.AddHandler(UIElement.MouseLeftButtonUpEvent, new MouseButtonEventHandler(Button_MouseLeftButtonUp), true);
private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
}
private void Button_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
}
OK, keep on going, but the next problem is coming, it didn't fire the ICommand in ViewModel, it called the Button_MouseLeftButtonDown, oh, god, I crazy
Anyone know how to call the ICommand in ViewModel?
Or another way to implement it?
You can use ICommand.Execute. So, your handler should be
private void Button_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
StartRecordCommand.Execute(null);
}
Try binding the IsPressed property of the Button with a TwoWay binding to a bool property called IsRecording in your ViewModel and start/stop your recording logic from inside the setter based on the new bool value (true would mean start).
Let me know if it works.

WPF, Some Event not fire up from Grid To UserControl

I have a UserControl where i override some event like this:
public class MyControl: UserControl
{
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonUp(e);
// handle mouse event up
}
}
I add this control to antother UserControl -> Grid, this Grid has MouseUp registered.
public class MyParent: UserControl
{
private void Grid_MouseUp(object sender, MouseButtonEventArgs e)
{
// handle grid mouse event
}
}
And in MyParent XAML i have simply:
<UserControl ... >
<Grid Name="Grid" MouseUp="Grid_MouseUp">
<my:MyControl Height="250" Width="310" Visibility="Visible" Opacity="1" IsEnabled="True" />
</Grid>
</UserControl>
What i notice is that when i release mouse over MyControl the event is captured by Grid and not routed to MyControl, why?
How can i receive MouseUp event inside MyControl class?
EDIT
With MouseDown all works as expected, only MouseUp not works... and both event are registered for parent Grid too, so what is the difference?
EDIT2
Ok, i think i found the problem, if i add MyControl to MyParent -> Grid directly in XAML, all works good, but if i add it programmatically "MyParentInstance.Grid.Children.Add(MyControlInstance)" then i have the problem above.
Is my code to add control correct?
Thanks.
RoutedEvents only work for the parents of the element, which is invoking the specific event. That means for you, that all parent controls of your grid will receive the event, but children will not. For more information about routed events see Routed Events Overview. To solve your problem I would suggest to register the MouseUp Event at MyControl:
<UserControl ... >
<Grid Name="Grid">
<my:MyControl MouseUp="Grid_MouseUp" Height="250" Width="310" Visibility="Visible" Opacity="1" IsEnabled="True" />
</Grid>
</UserControl>
I did not come across the answer to this question after an extensive search of forums so I'm providing my solution here although this post is dated. I made use of event handlers that I'd already built in the child control by making them public. When the ChildControl user control is added to the Window at the top level, these fire. When nested within parent, the event had to be handled at the parent level.
When programmatically adding a control, add the event handler in the Parent control, such as:
class Parent : UIElement
{
//...
void Parent_AddChildControl()
{
ChildControl child = new ChildControl();
child.MouseEnter += child_MouseEnter;
UiElementX.Children.Add(child);
}
void child_MouseEnter( object sender , MouseEventArgs e )
{
((ChildControl)sender).ChildControl_MouseEnter(sender, e);
}
}
class ChildControl : UIElement
{
//...
public void ChildControl_MouseEnter(object sender, MouseEventArgs e)
{
//event handling
}
}

Categories