Add Click Event To Button Template? - c#

I am writing a WPF app, and need to be able to bind a method call with arguments to a button's Click event (or equivalent).
Until now, just for testing I was programmatically creating the buttons and setting them as children of the necessary StackPanel.
foreach (Page p in pages)
{
Button newTestBtn = new Button();
newTestBtn.Content = p.Title;
newTestBtn.Name = p.Name.Replace(" ", string.Empty);
newTestBtn.FontSize = 14;
newTestBtn.Margin = new Thickness(2, 0, 2, 2);
newTestBtn.Background = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF414141"));
newTestBtn.Click += (s, e) => { SwitchPage(p); };
hp.CreateNewTestEntry(newTestBtn);
}
After deciding this was far from the best way of doing this, I set up a more permanent solution by having an ItemsControl bound to an ObservableCollection of a serialized class.
I have hit a new problem though, applying an ICommand or RoutedEventArg to a Click or Command on a button simply doesn't seem to work.
foreach (Page p in pages)
{
TempCl tcl = new TempCl();
tcl.ToolName = p.Title;
tcl.TriggerEvent = SwitchPage(p);
tempHolder.Add(tcl);
}
Google & StackOverflow searching hasn't yielded any easy ways of doing this and I'm a bit stuck.
<Button Margin="0, 0, 0, 2" Content="{Binding ToolName}" Command="{Binding TriggerEvent}" FontSize="14" Background="#FF414141"/>
Does anyone know the correct way of doing this without adding a lengthy class to do this? There must be an elegant solution.

First, if You want a more maintainable app in the future, you have to start by studying the basics of MVVM approach. Basically, your code can be separated into 3 different stuff: Model - ViewModel - View.
As it was mentioned in the comments to your question, it is required to bind Command dependency property of a Button to a property of ICommand type in your ViewModel (that should be a DataContext for your View or your Control, etc) and specify CommandParameter as well (it will be passed to the Command).
Generic ICommand implementation (e.g. DelegateCommand<T> or RelayCommand<T>) allows you to specify the parameter, which in your case might be whatever you like that describes the target of navigation).
You can find a more detailed sample with Page navigation HERE.
Also, it might be useful for you to figure out and use Prism Library. It provides a lot of functionality around modularity, Views navigation in a single Window, etc.

Related

Prism.WPF: Change MainWindow Window.Effect on Navigation

The application is a Prism Application in WPF using C#.
I am attempting to assign a BlurEffect to the Window.Effect property when a button is clicked on the navigation menu.
I have the Window.Effect bound to a property in my viewmodel.
<Window ... other properties ..
Effect = {Binding Fuzzy}>
and the Fuzzy property in the ViewModel.
private Effect _fuzzy;
public Effect Fuzzy { get => _fuzzy; set => SetProperty(ref _fuzzy, value); }
What I am attempting to implement is that when a button is clicked on the navigation menu that the window will blur while a UserControl is loading.
I have tried to implement the change in the Navigate method.
private void Navigate(string viewName)
{
PerformBlur();
_regionManager.RequestNavigate("ContentRegion", viewName);
}
private void PerformBlur()
{
BlurEffect blur = new BlurEffect();
blur.Radius = 4;
var ef = blur;
_fuzzy = ef; //I've tried Fuzzy = ef too
}
But that doesn't work.
I need to make the change to the window effect before it attempts to navigate, and I haven't been able to figure out how to make that happen. I have a feeling that the easiest way to do this would be to use a click event rather than a command, and then call the command in the viewmodel from the codebehind. However, that doesn't seem to be the proper implementation when using MVVM. Any suggestions on how to implement this functionality would be greatly appreciated.
(Bonus points if you can tell me how to animate the blur. lol)
I have a feeling that the easiest way to do this would be to use a click event rather than a command, and then call the command in the viewmodel from the codebehind. However, that doesn't seem to be the proper implementation when using MVVM.
Invoking the command programmatically from the code-behind of the view is not any worse than invoking it from the XAML markup of the very same view as far as MVVM is concerned.
MVVM is not about eliminating code from the views. It's about separation of concerns. You can implement an entire view programmtically in a C# without using XAML at all and still be fully compliant with MVVM.
Trying to do fairly complex stuff in XAML just because you possible can is generally considered as an antipattern. Remember that XAML is a markup language. C# is a much more expressive and concise language so if you can solve your issue by writing some code, then this is most probably what you should do.
Taking a look at the prism source code, I can see iregionmanager is full of abstracted interfaces.
( Wow. I don't know why it still surprises me but prism is very complicated ).
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Regions/IRegion.cs
That includes IRegionNavigationService
https://github.com/PrismLibrary/Prism/blob/master/src/Wpf/Prism.Wpf/Regions/IRegionNavigationService.cs
You could therefore override pretty much any functionality you like, if you wanted to.
Notice though, the two events :
/// <summary>
/// Raised when the region is about to be navigated to content.
/// </summary>
event EventHandler<RegionNavigationEventArgs> Navigating;
/// <summary>
/// Raised when the region is navigated to content.
/// </summary>
event EventHandler<RegionNavigationEventArgs> Navigated;
Looks to me like "all" you need is a reference to your region navigation service in the view.
Handle those two events to set blur then remove blur.
You could then do navigation in code behind or viewmodel. Whichever suits.
If you wanted to decouple viewmodel from view, you could use the eventaggregator.
There is another option though.
You don't explain exactly what you have there. So let's imagine and consider a better way to do this.
Say you have a set content of a set control you're always navigating. That's being switched out as you navigate for a new view whose datacontext is a new viewmodel.
You could bind an attached property from the window to the datacontext of that.
In that property you can have a change callback.
In a base viewmodel you could add an IsLoaded bool property which is initialy false.
When your dependency property callback returns null or false then you blur.
You change the viewmodel property to false in the current viewmodel when you start to navigate. The window blurs. The content is switched out and you get a new viewmodel. Once navigation completes you set that ILoaded true. You callback un blurs the window.

UWP - Pass event to control (tunneling)

I have a UWP question about inheriting/ passing a event to a user control from the parent view to child.
I created a user control to display text overlays (see code below). We had a parent view that would display an overlay when the window is resized (see code below). The overlay would display the dimensions of the window when this even is triggered.
I moved the overlay to a user control and now I'm trying to pass that resized event to the overlay control. The hope is that we can register more events to the overlay control so it can display more then the resize
information. However, I'm not sure the best way to do this. My first idea was inheriting from the view, so i could just listen to the event from the overlay control, but that resulted in errors.
I believe due to the fact that the parent view has a ViewModel (i also created one for the overlay, not sure if its actually needed yet).
I have been reading about a lot of possible ways to do this, but I'm not sure which would be the best way to do this. Does anyone have any insight on this issue ? I would be open to suggestions, links, or just a general answer of
what is the best way to achieve this in our project.
Parent view
User Control
Parent Event
Control class
Some information i have been reading about:
https://documentation.devexpress.com/WPF/17449/MVVM-Framework/ViewModels/ViewModel-relationships-ISupportParentViewModel
https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/preview-events
https://social.msdn.microsoft.com/Forums/windows/en-US/742077f6-e875-44d1-8bc4-6e6516db9eda/passing-the-parent-control-event-to-child-controls?forum=winforms
https://learn.microsoft.com/en-us/windows/uwp/xaml-platform/events-and-routed-events-overview
http://irisclasson.com/2013/12/10/passing-event-arguments-from-xaml-in-windows-store-apps-inputconverter-inputconverterparameter-etc/
https://learn.microsoft.com/en-us/windows/uwp/launch-resume/how-to-create-and-consume-an-app-service
Update
Adding the viewModel to the parent viewModel (terminal), and passing it to the control via the Datacontext did not work
As you're already using MVVM, I'd recommend going the full route utilizing "Interactivity", "Commands", and "child ViewModels". This is a commonly used patter in MVVM WPF applications, and can be applied to UWP apps as well.
Using "Interactivity" and interactions
The interactivity / behaviors library from Microsoft allows you to bind events in XAML to an ICommand in the ViewModel. You can get the managed NuGet package here.
From the official examples on GitHub, shortened:
<Button x:Name="button1" Content="Increment">
<Interactivity:Interaction.Behaviors>
<Interactions:EventTriggerBehavior EventName="Click" SourceObject="{Binding ElementName=button1}">
<Interactions:InvokeCommandAction Command="{Binding UpdateCountCommand}"/>
</Interactions:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>
</Button>
Forward command data to child ViewModel
Having this event now routed to your command in your parent ViewModel, you can now either call your overlay ViewModel and pass the info directly to it:
private readony IOverlayViewModel _overlayViewModel;
public ICommand UpdateCountCommand { get; set; }
ctor(IOverlayViewModel overlayViewModel)
{
_overlayViewModel = overlayViewModel;
UpdatedCountCommand = new MyICommandImplementation(UpdatedCountCommand_Executed);
}
private void UpdatedCountCommand_Executed(/* Add correct method signature */)
{
// If needed, retrieve data from parameter...
// Update overlay ViewModel text
_overlayViewModel.Text = ""; // Whichever text was calculated before
}
Or you use a messenger (mediator pattern) to send it to an overlay.
I was misusing the bindings. x:Bind and Binding are using different types of context. For this binding to work we would need to set the parent's element Datacontext to 'this'. x:Bind on the other hand does this implicitly.
<views:OverlayView DataContext="{x:Bind ViewModel.Overlay}"></views:OverlayView>

MVVM Violations

I want to clear some issues about MVVM violation.Becuase of this i've created a solution with some projects to demonstrate the cases.
Here's the definition (Projects) of the solution :
View (its a WPF Class Libraray and obviously it has the views)
ViewModel (its a Class Libraray and obviously it has the viewmodels )
Model (its a Class Libraray and obviously it has the models)
Domain (its a Class Libraray and it has the application dataModels )
Core (its a Class Libraray and it has the core of wpf like RelayCommnd or EventToCommand)
Application ( its a wpf application and the startup project)
ExternalCustomControl (its a wpf custom control library created by an imaginary third party company)
I Offer you to download the whole solution to understand better from
Here
First Issue :
I've an EventToCommand in the MainWindow.xaml for Closing Event of the window and attached it to MainWindowClosingCommand with the PassEventArgsToCommand set to True,then,in the MainViewModel there's a handler for the command named OnMainWindowClosing
private void OnMainWindowClosing(object parameter)
{
var arg = parameter as CancelEventArgs;
// What is the best way to show message dialog to user?
// Do i have to send message to the View to show the messageBox dialog and then the window send me the answer back to continue?
// What about IMessageBoxService? Doesn't it violates MVVM?
// Doesn't following code violates the MVVM?
// Cancel the Closing of a Window isnt a UI side duty?
arg.Cancel = true;
}
and totally whenever you want to set e.Handled or e.Cancel you face this issue.So do you know any other way that doesn't need to cast parameter as CancelEventArgs ?
Second Issue :
I've an EventToCommand in the MainWindow.xaml for PreviewMouseDown Event of the Grid and attached it to MouseClickCommand with the PassEventArgsToCommand set to True,then,in the MainViewModel there's a handler for the command named OnMouseClick:
private void OnMouseClick(object parameter)
{
// var arg = parameter as MouseButtonEventArgs;
// This is the violation of MVVM : To cast the parameter to MouseButtonEventArgs i have to add a refrence
// to PresentationCore.dll in the ViewModel Project
// The next and worse step is that in most cases we need to know the Original Source of the event
// (maybe its a StackPanel or a Label or etc) and this again vioaltes the MVVM
// So Whats the WorkAround?
}
Third Issue :
I used the ThirdParty Control(Imagine Infragistics or DevExpress or any other third party control but here as an example i created the imaginary control in my solution as the ExternalCustomControl Project) in my MainWindow Like this :
<thirdParty:ThirdPartyCustomControl Grid.Row="1"
ItemsSource="{Binding MyItemsSource,Converter={StaticResource converterKey}}" />
and ThirdPartyCustomControl has a property of type IEnumarabe<CustomControlDataModel> (CustomControlDataModel is a type that exists in the ExternalCustomControl assembly) But as you know if you want to create a property in MainViewModel for the control with the type CustomControlDataModel you have to add a refrence to ExternalCustomControl.dll in ViewModel Project and this violates MVVM so i created a type named MyDataModel and bound the ItemsSource of the control to MyItemsSource property in MainViewModel :
// If i define MyItemsSource as List<CustomControlDataModel> i have to add a refrence to ExternalCustomControl.dll
// and i think its again violate the MVVM (because ExternalCustomControl.dll is a UI Side Controls Assembly)
public List<MyDataModel> MyItemsSource { get; set; }
so i bound a property of type CustomControlDataModel to a property of type MyDataModel and of course i need a Converter :
public object Convert(object value, Type targetType, object parameter, c System.Globalization.CultureInfo culture)
{
// Imagine when the source data (MyDataModel) is huge (for example 1 milion) it (this dummy Conversion)
// affects the performance
if (value is List<MyDataModel>)
{
var result = new List<CustomControlDataModel>();
(value as List<MyDataModel>).ForEach(myVal =>
{
var custDataModel = new CustomControlDataModel();
custDataModel.ID = myVal.ID;
custDataModel.Name = myVal.Name;
custDataModel.Age = myVal.Age;
result.Add(custDataModel);
});
return result;
}
return value;
}
and the question is do you know any better way than this dummy conversion or you normally add your third party assemblies to your view and viewmodel both?
These are the issues that i've faced and i'll be appreciated if you add more if you know the other issues and share your expertise to everyone.
Upadte:
For the MessageBox Part of first issue i suggest this link
MesageBox
Thanks.
Excellent questions!
1) I personally believe you are correct, the use of a service violates MVVM. I wrote a very lengthy article on this exact topic a few weeks ago titled Implementing Dialog Boxes in MVVM. In that article I make the case for a "pure" solution to the overall problem of MVVM dialog boxes but it took 11 pages to explain how I arrived at that design. Fortunately the actual implementation is very straightforward, is similar to data templating, supports the multiple-project design you've specified and it works with 3rd party libraries. Have a read, I always appreciate objective feedback.
2) If you're using MVVM Lite then EventToCommand allows you to specify an argument converter. Here's an example where I used it to convert the window mouse move message argument to an equivalent representation in my view model:
<i:EventTrigger EventName="MouseMove">
<cmd:EventToCommand Command="{Binding ElementName=_this, Mode=OneWay, Path=MouseMoveCommand}" PassEventArgsToCommand="True" EventArgsConverter="{StaticResource MouseEventArgsConverter}" />
</i:EventTrigger>
3) If I understand your question correctly I add a reference to both the view and view model projects, at least when that is my project structure. To be perfectly honest though I usually place my view and view models in the same project e.g. MyProject.UI, with everything sorted by category folders. I saw this done during a contract I was working on for a major international firm and in practice it works really well because you typically edit a view and it's corresponding view model at the same time; having them side-by-side in the solution window really does make the whole development process easier. Obviously some purists don't like it much, but personally I don't believe that simply having them in the same project breaks MVVM provided you still adhere strictly to that architecture. I've also never had it create any problems with unit testing etc where you need to create view models only.
my closing code looks like this, i dont think that is violating mvvm
xaml
<Window>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Closing">
<cmd:EventToCommand Command="{Binding ClosingCommand}" PassEventArgsToCommand="True" />
</i:EventTrigger>
</i:Interaction.Triggers>
mainviewmodel.cs
public ICommand ClosingCommand
{
get
{
return this._closingCommand ?? (this._closingCommand = new DelegateCommand<CancelEventArgs>((args) =>
{
//i set a property in app.xaml.cs when i shut down the app there with
//Application.Current.Shutdown();
if (App.IsShutDown) return;
if (this.HasChanges)
{
var result = _msgService.Show("blup blup", "blup", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (result == MessageBoxResult.No)
{
args.Cancel = true;
}
}
else
{
var result = MessageBox.Show("Blup blup", "blup", MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No);
if (result == MessageBoxResult.No)
{
args.Cancel = true;
}
}
}));
}
}
third issue (ThirdParty Control): i dont get your problem if the control need a type of collection then expose these colelction through your VM.
second issue: well is hard to say. i use something like this, and i would say is mvvm like ;)
<DataGrid x:Name="myGrd">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<Commanding:EventToCommand Command="{Binding Path=OpenCommand}"
CommandParameter="{Binding ElementName=myGrd, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
and at the end i do mvvm but always in mind to do things simple
For your second issue I think you just need to rethink what it is you are trying to achieve and why.
If you are wiring up a handler on a grid, and then making decisions based on what specific UI element of that grid was clicked in code you are probably doing things wrong. A Command called OnMouseClick is a bit of a code smell too, because that command says nothing. A command would be something like UserSelectedCommand, or GrapefuitSliceRequestedCommand ... i.e. much more specific than just a general 'something was clicked'.
You want to try and break that 'something' down into the logic for issue a clear and definitive command at that point rather than trying to work with MouseClickEventArgs and deciding in code what that means - your UI should be deciding what that means, and issuing commands to your VM.
So your individual UI elements should have the commands and binding, rather than trying to set commands at the layout UI level. If an Image is clicked that means something specific, if a row of a DataGrid is clicked that means something specific, and if a Slider is dragged that means something specific. Create your XAML so that it triggers those specific commands and don't push that responsibility up to a vague 'my whole ui was clicked and now I will use code to find out what exactly' way of thinking.

Caliburn.Micro - ShowDialog() how to close the dialog?

EDIT:
New Information, just managed to get a logger working (I honestly had no idea cm had one!) and i'm given this message when attempting to use TryClose().
TryClose requires a parent IConductor or a view with a Close method or IsOpen property
I have been stuck on this for a number of days now, and research has turned up zero, I tried posting a question previously about this issue but it received no answers so i assume I didn't word it correctly.
I have a view and viewmodel ContentView/Model which has the following code in them:
ContentView:
<MenuItem Header="New Project" x:Name="OpenProject" cal:Message.Attach="[Event Click] = [Action NewProject()]"/>
ContentViewModel:
public void NewProject()
{
NewProjectViewModel viewModel = new NewProjectViewModel(_projectManager);
_windowManager.ShowWindow(viewModel);
//If the result is true, we have a new project, otherwise they cancelled the window.
if (viewModel.Result)
{
Project newP = new Project(0, viewModel.ProjectNo, viewModel.ProjectName, 0, 0);
_projectManager.Insert(newP);
}
}
and the viewmodel NewProjectViewModel has the following:
public void Create()
{
this.Result = true;
TryClose(true);
}
which is called in the same was as previously using an message.attach on the OK button of the dialog.
However the issue is that TryClose() always fails to close the dialog, and as i don't have the source of caliburn.micro i can't debug inside TryClose() however doing (GetView() As Window).Close() also fails because GetView() always returns null.
I'm at a complete loss as to how i can close this dialog, so any help or suggestions would be greatly appreciated.
EDIT:
Since i seem to be getting no answers on this, where as previous questions do, I'll assume i have information missing. In an attempt to understand the issue i think it may have something to with using the view first approach.
In the NewProjectView i have the following:
xmlns:cal="http://www.caliburnproject.org"
cal:Bind.Model="ShippingClient.ViewModels.NewProjectViewModel"
This is used to bind the viewmodel rather than the automatic way that is usually used, perhaps this is why GetView() returns null?
You are going to absolutely kick yourself:
Remove the cal:Bind.Model and cal:View.Model bindings...
If you are working ViewModel-First (i.e. you are creating a viewmodel and showing it using WindowManager or in a conductor) all the binding stuff that glues the viewmodel to the view is done for you by CM.
In this case you shouldn't use any View-First bindings. What you are essentially doing is newing up another instance of your VM and binding that to your view... so you have two viewmodels in the background, one wired up nicely but not bound any more, and a non-wired up instance which is bound to your view but doesn't know about the conductor.
Just remove any bindings to the viewmodel in your view and it will all work!

How to Handle MessageBox Dialogs When Using the MVVM Pattern(MVVM Light ToolKit)

I am making a windows phone 7 and trying to do it using MVVM. I would like to keep my view model as clean as possible but I am unsure on how to make a dialog box. I am using MVVM light and I know they have Messaging system or something but not really sure how to use it.
I would like to use Guide.BeginShowMessageBox as this seems to give more features than the standard dialog box.
How can I do this without breaking the MVVM pattern. AS when I load up the view I want to have a loaded trigger to be triggered and then check some conditions. If conditions are met show the Dialog.
// Vm
public RelayCommand MainPageLoaded
{
get
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
// breaks MVVM now as have view code in viewmodel. Need to take out somehow
Guide.BeginShowMessageBox("Test", "Test network", new List<string>() { "Yes", "No" }, 0, MessageBoxIcon.Warning, asyncResult =>
{
int? returned = Guide.EndShowMessageBox(asyncResult);
// if yes then work offline mode? Maybe another property in ViewModel will get set to say offline mode?
}, null);
}
return null;
}
set
{
// Not sure what to put here.
}
}
// View
<i:Interaction.Triggers>
<i:EventTrigger>
<GalaSoft_MvvmLight_Command:EventToCommand Command="{Binding MainPageLoaded}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Edit
Another problem I am having is. I have a list that is bound to some data that is stored in this property
public ObservableCollection<ContactGroup> ContactGroups { get; set; }
then on tap I have a relaycommand that should be triggered
public ICommand GroupContactTapped
{
get
{
return new RelayCommand<GestureEventArgs>(e =>
{
var selectedTextBlock = e.OriginalSource as TextBlock;
MessageBox.Show(selectedTextBlock.Tag.ToString());
});
}
}
Yet I don't know how to find which object was "tapped" without casting the source to a textblock.
Assuming that you have one mainpage/view that hosts all the other views, like a mainwindow:
I send a message event from the viewmodels, and the dialog box is handled in the code behind of the main window. This is the only codebehind I have in my project so I find it acceptable that the rest of the project can be strictly MVVM, with this one exception.
I send the message with the following (converted from VB so it might need work):
object message = new DialogMessage("YourMessage", YourFunctionThatHandlesCallback) {
Button = MessageBoxButton.YesNo,
Caption = "Caption Goes Here"
};
Messenger.Default.Send(message);
I register for the dialog box with the following in the main page code behind:
Partial Public Class MainWindow
Inherits Window
Public Sub New()
InitializeComponent()
''single initialization of messanger for catching message box
Messenger.[Default].Register(Of DialogMessage)(Me, Sub(msg)
Dim result = MessageBox.Show(msg.Content, msg.Caption, msg.Button, MessageBoxImage.Warning)
''Send callback
msg.ProcessCallback(result)
End Sub)
End Sub
End Class
I could not succesfully convert the C# lambda so I had to leave it in VB. Hope this helps
There is a MessageBoxService in the Cimbalino Phone Windows Toolkit!
You can use that in a MVVM architecture.
What it truly means to follow "the MVVM pattern" is a very subjective thing.
For instance, some people will say you shouldn't show/launch a messagebox (of any type) from the VM, while others will say this is fine.
As with any ambiguity, you'll need to balance adherence to a pattern, with what's most appropriate for a specific project, with what's appropriate for the people developing and maintaining the code base.
In terms of MvvmLight, the messaging system it uses is for communicating from a viewmodel to either another viewmodel or a view, not for displaying messages to the user.
If you are going to use Guide.BeginShowMessageBox, particularly from a viewmodel, beware that it is non-blocking. If you want it to behave like a "regular" MessageBox you'll need to use it with a ManualResetEvent so that it's not possible to continue to interact with the app while the messagebox is displayed.

Categories

Resources