Cannot Create custom command in WPF Application - c#

Overview
I have a WPF application written in C# .NET 4.0. There are a number of buttons in this project. Currently, every EventHandler for the button click events calls the same method, with a different valued parameter. As this is all done in C# code, it is rather unwieldy and involves a lot of copy/paste.
I'd much rather use a Command, rather than a bunch of EventHandlers, as it keeps my code cleaner.
Problem
No matter what I do, I cannot get the XAML to accept the new command:
<Window.CommandBindings>
<CommandBinding Command="ButtonClick" Executed="ButtonClick_Executed" />
</Window.CommandBindings>
The above code has an error: "CommandConverter cannot convert from System.String". This error exists on the Command="ButtonClick".
Research/Attempts
I've tried everything I can think of to make this work to no avail. I've looked at at least 30 different blog posts/tutorials/etc. on how to properly do this, so I don't have an actual list for you. I saw some people using the Binding keyword, so I attempted: Command="{Binding ButtonClick}", but apparently that's not allowed inside a CommandBinding section. I have also tried Command="{x:Static ButtonClick}", but that doesn't work either.
As for the "ButtonClick" itself, I have tried several possible values there. "ButtonClick" is an arbitrary text string that does not correspond to any class/method/variable. My original understanding of this parameter was for the value to be simply an identifying string. I have also tried using an actual method in the same class "ButtonClick_Executed". I have also tried using a class name which implements ICommand (Commands), as well as a method inside that class (Commands.ButtonClick_Executed).
For reference, I also tried doing this in the codebehind as per WPF: Binding to commands in code behind, but I couldn't make that work either.
Code
Code is abridged to what is relevant. From what I understand, the Button's Command parameter needs to match the CommandBinding's Command value. I have not added this in yet, since I can't even get the CommandBinding to work. All code is in the same namespace, in a single project.
MainWindow.xaml:
<Window x:Class="T9Messager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="600" Width="540">
<Window.CommandBindings>
<CommandBinding Command="ButtonClick" Executed="ButtonClick_Executed" />
</Window.CommandBindings>
<Grid Margin="0,0,0,0">
<Button x:Name="Button1" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="165" Height="113">
<TextBlock HorizontalAlignment="Center" TextAlignment="Center" FontSize="18" FontWeight="Bold">1</TextBlock>
</Button>
... More buttons ...
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window, IObserver<Model>
{
private Controller controller;
private IDisposable Unsubscriber;
public MainWindow()
{
Model model = new Model();
Controller controller = new Controller(model);
this.controller = controller; // MainWindow view = new MainWindow(c);
Unsubscriber = model.Subscribe(this);
InitializeComponent();
}
// This is the method I want to run
public void ButtonClick_Executed(object sender, EventArgs ev)
{
Console.WriteLine("Command Executed");
}
// I want to avoid having a bunch of these
private void Button_Click_1(object sender, RoutedEventArgs e)
{
controller.HandleButtonClick('1');
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
controller.HandleButtonClick('2');
}
... More ButtonClick Events ...
public void OnCompleted()
{
Unsubscriber.Dispose();
}
public void OnError(Exception error)
{
throw new NotImplementedException();
}
public void OnNext(Model value)
{
tb_text.Text = value.text;
}
}
Commands.cs:
public class Commands : ICommand
{
public Commands()
{
CommandBinding ButtonClickBinding = new CommandBinding(ButtonClickCommand);
CommandManager.RegisterClassCommandBinding(typeof(Commands), ButtonClickBinding);
}
private RoutedUICommand ButtonClick = new RoutedUICommand("ButtonClick", "ButtonClick", typeof(Commands));
public RoutedCommand ButtonClickCommand
{
get { return ButtonClick; }
}
// Getting any of the following 3 methods to execute would be fine
public static void test()
{
}
public void Execute(object parameter)
{
ButtonClick_Executed(null, null);
}
public void ButtonClick_Executed(object sender, EventArgs ev)
{
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
}
Any and all help is greatly appreciated. Please let me know if you require any additional information.
Update
I have attempted the answer proposed by Herdo. The new error I got was The namespace prefix "T9Messager" is not defined. After research, my XAML now opens like this:
<Window x:Class="T9Messager.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:T9Messager="clr-namespace:T9Messager"
Title="MainWindow" Height="600" Width="540">
This seems to have fixed that particular issue, but now it looks as if the XAML is completely ignoring the Command parameter. Command="T9Messager:Commands.ButtonClick" creates the error Value cannot be null. Parameter name: value.

This ButtonClick is a String:
<CommandBinding Command="ButtonClick" Executed="ButtonClick_Executed" />
You need to write it as a member of the class (note that you need to add the namespace in the window declaration):
<Window xmlns:t9m="clr-namespace:T9Messager"
...>
<CommandBinding Command="t9m:Commands.ButtonClick"
Executed="ButtonClick_Executed" />

In my code, I removed the "x:Static" from 'Command="{x:Static ButtonClick}"' and worked.

Related

Click-Event from MainWindow.xaml.cs in App.xaml

so i want to outsource some things from MainWindow.xaml to App.xaml like this for example :
<Application x:Class="SVGTesting.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<DataTemplate DataType="{x:Type ContentControl}" x:Key="Test1">
<StackPanel Orientation="Horizontal">
<Button Content="Button1" Click="Button_Click" x:Name="Button1"/>
<Button Content="Button2" Click="Button_Click" x:Name="Button2"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
In MainWindow.xaml then i have something like this
<ContentControl ContentTemplate="{StaticResource Test1}"/>
But now VS says that i cannot use the function "Button_Click" because its not in the codebehind from App.xaml. So how can i call this function from MainWindow in App.xaml?
Is there any way? I don't want answers like MVVM or Command. If it's not possible to solve then WPF is unfortunately useless for me.
Thanks and Greetings.
This is not the easiest thing to do as WPF expect things to be done in its own way. But there's few options, from easiest to hardest.
1. Don't do anything
Easiest way is to keep your data templates inside the MainWindow.xaml.
2. Use Commands instead of event handlers
You currently have event handlers defined like this:
<Button Content="Button1" Click="Button_Click"
"More-WPF way" of doing this would be to replace Click's event handler with a command with this quite cumbersome syntax:
<Button Content="Test" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.OnClickCommand}"></Button>
And then define the command in your MainWindow:
public ICommand OnButtonClick
{
get
{
return new Command(() =>
{
this.Text.Text = "Updated";
});
}
}
3. Define the event handlers in App.xaml.cs and use that to route the event handlers
I don't recommend this as it get tiresome to keep things synced but it's possible. Create and event handler in App.xaml.cs:
public partial class App : Application
{
private void Button_Click(object sender, RoutedEventArgs e)
{
}
}
Then use the sender to access the MainWindow instance and call it's method:
private void Button_Click(object sender, RoutedEventArgs e)
{
var mainWindow = (MainWindow)Window.GetWindow((DependencyObject)sender);
mainWindow.DoWork();
}
In my second example Command is defined like the following:
public class Command : ICommand
{
public delegate void ICommandOnExecute();
private ICommandOnExecute _execute;
public event EventHandler CanExecuteChanged;
public Command(ICommandOnExecute onExecuteMethod)
{
_execute = onExecuteMethod;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
_execute?.Invoke();
}
}
You can't do it. See MSDN documentation for Code-Behind:
The event handlers you write must be instance methods defined by the
partial class within the namespace identified by x:Class. You cannot
qualify the name of an event handler to instruct a XAML processor to
look for that handler in a different class scope. You also cannot use
a static method as an event handler.
In WPF you can use a behaviors instead.
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
<Button Content="btnWithBehavior">
<i:Interaction.Behaviors>
<local:HandleButtonClick/>
</i:Interaction.Behaviors>
</Button>
public class HandleButtonClick : Behavior<Button>
{
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Click += AssociatedObject_Click; ;
}
private void AssociatedObject_Click(object sender, RoutedEventArgs e)
{
//Move your MainWindow.Button_Click here;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.Click -= AssociatedObject_Click;
}
}

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.

How to access WPF MainWindow Controls from my own .cs file

I am a NOVICE and am very much struggling with what seems should be a very simple task. How do I modify a property of a MainWindow TextBlock, from another cs file. An exact code solution would be extremely helpful.
Below is the stripped down code. Is my use of static class causing me extra issues?
In File: MainWindow.xaml
<Window x:Class="TestApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock x:Name="TextBlock1" HorizontalAlignment="Left" Margin="107,71,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top"/>
</Grid>
</Window>
In File: MainWindow.xaml.cs
namespace TestApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
TextBlock1.Text = "Setting Text from MainWindow";
MyProgram.myProgramStart();
}
}
}
In File: CodeFile1.cs
namespace TestApp1
{
public static class MyProgram
{
public static void myProgramStart()
{
// ... blah blah blah
// I want to do something like follows, but won't compile
MainWindow.TextBlock1.Text = "Setting Text from My Program";
}
}
}
Because nobody else has actually answered the question I'm going to tell you how to achieve what you want, but do listen to the posters who said that in a real application you would use MVVM. However there are times when you need to do what you ask so the code you need is:
((MainWindow)System.Windows.Application.Current.MainWindow).TextBlock1.Text = "Setting Text from My Program";
You can simply achieve this using MVVM. You shouldn't directly access controls in View using its name from another class. You have to use binding properties.
First of all, add a class. This will be your ViewModel. Add your properties to this class which will be binded to your input controls in your View.
Student ViewModel
public class Student
{
public string Name
{
get { return "Setting Text from My Program"; }
}
}
App.Config
Now you have add to this View Model as a resource in your App.Config file. First, add the name space reference to your app.config where your VM resides.
[xmlns:local="clr-namespace:WpfApplication2]. Add your VM class as a resource by specifying your View Model class name (student).
<Application x:Class="WpfApplication2.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:local="clr-namespace:WpfApplication2">
<Application.Resources>
<local:Student x:Key="Student" />
</Application.Resources>
</Application>
MainWindow.xaml
Set the DataContext with the resource key added to App.config and set the binding to the property defined in the Student View Model.
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{StaticResource Student}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Text="{Binding Name}" Height="23" HorizontalAlignment="Left" Margin="127,124,0,0" Name="textBlock1" VerticalAlignment="Top" Width="214" />
</Grid>
</Window>
Basically there are more than 2-3 methods. Given method is quite easier to understand & handle.
You can access MainWindow controls by following codes (1),(2),(3),(4).
In File: MainWindow.xaml.cs
public partial class MainWindow
{
internal static MainWindow Main; //(1) Declare object as static
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Main =this; //(2) Defined Main (IMP)
var AnyClassORWindow=new Class1(); //Initialize another Class
AnyClassORWindow.ShowValue();
}
}
In File: Class1.cs
internal class Class1 : MainWindow //(3) Inherited
{
internal void Display()
{
MessageBox.Show(Main.TextBox1.Text); //(4) Access MainWindow Controls by adding 'Main' before it.
}
}
Notes:-
It's good practice to use code (2) after window LOADED not in CONSTRUCTOR.
Code (2) in constructor may leave run-time problems.
Another simple method is to use 'ref MainWindow_field' by passing to each class's Constructor OR assign '(MainWindow) Application.Current.MainWindow' to static Main.
Use MVVM pattern to access properties of the control and modify them:
public class Student
{
public Student()
{
}
public string Name
{
get { return "Setting Text from My Program"; }
}
}
Set the DataContext of the XAML in the code behind:
this.DataContext = new Student();
Bind the Text property to Name:
<TextBlock Text="{Binding Name}"/>
As for why it won't compile, I will assume the compiler error you are getting is...
An object reference is required for the non-static field, method, or property 'TestApp1.MainWindow.TextBlock1'
This happens because you are trying to access an TextBlock1 as if it were static. As #JeffRSon stated, create an instance of your MainWindow class first.
// Create an instance of MainWindow
var mainWindow = new MainWindow();
mainWindow.TextBlock1.Text = "Setting Text from My Program";
I assume you may want to display the window as well...
mainWindow.ShowDialog();
You need to create an instance of MainWindow.
But there shouldn't be a reason to do that because it will be done automagically in an WPF app. Unless you have specific reason to do that (which I doubt because of this question and because you say you're a novice).
To extend on what Nathan said, I used a safe cast:
(System.Windows.Application.Current.MainWindow as MainWindow)?.TextBlock1.Text = "Setting Text from My Program";
Note the comments on the answer Nathan gave. This isn't ideal but it works.
you can use App.Current.MainWindow anywhere in your app :)
You should also use the FindChild method to find your component ...
MyProgram.cs :
public static class MyProgram
{
public static void myProgramStart()
{
// ... blah blah blah
// I want to do something like follows, but won't compile
//MainWindow.TextBlock1.Text = "Setting Text from My Program";
var tb = FindChild3<TextBlock>(App.Current.MainWindow, "TextBlock1");
tb.Text = "Setting Text from My Program";
}
public static T FindChild3<T>(DependencyObject depObj, string childName)
where T : DependencyObject
{
// Confirm obj is valid.
if (depObj == null) return null;
// success case
if (depObj is T && ((FrameworkElement)depObj).Name == childName)
return depObj as T;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
//DFS
var obj = FindChild3<T>(child, childName);
if (obj != null)
return obj;
}
return null;
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
MyProgram.myProgramStart();
}
}
This trick worked for me :)
GoodLuck ✌️

wpf Usercontrol in usercontrol no response

I have a strange problem in my project. There are pages made from usercontrol and menu bar (also usercontrol).
Here is my usercontrol that contains few buttons
public partial class UpperBar : UserControl
{
public UpperBar()
{
InitializeComponent();
}
public event EventHandler EventbtClicked;
private void btConnect_Click(object sender, System.Windows.RoutedEventArgs e)
{
EventbtClicked(this, e);
}
}
I added this in my page as follows:
<local:UpperBar VerticalAlignment="Top" Grid.Row="0" Height="78" Grid.ColumnSpan="3" Margin="0,2,0,0"/>
And in my page tried to call event:
public PageStatus()
{
InitializeComponent();
Plc.ExecuteRefresh += new EventHandler(RefreshLeds);
UpperBar.EventbtCliced += new EventHandler(UpperBatButtonClick);
}
protected void UpperBarButtonClick(object sender, EventArgs e)
{
//do something
}
But I can't access my event using this UpperBar.EventbtCliced, why ?
You need to access the instance of your class UpperBar in PageStatus, not the class UpperBar itself!
The easiest way for you here:
Name your UpperBar in your XAML, example:
<local:UpperBar x:Name="_myBar" x:FieldModifier="private"/>
Then use this instance in your PageStatus.xaml.cs:
public partial class MainWindow : Window {
public MainWindow()
{
InitializeComponent();
_myBar.EventbtClicked += new EventHandler(UpperBarButtonClick);
}
protected void UpperBarButtonClick(object sender, EventArgs e)
{
//do something
}
}
Now if you are working seriously in WPF, you should really learn about Databinding and MVVM, catching event this way is not the best way to do it at all.
You should use Custom Command (RoutedUICommand) rather than bubbling event from user control.
here are some steps to follow in contrast to your approach:
1: create class myCustomCommand.
namespace WpfApplication1
{
public class myCustomCommand.
{
private static RoutedUICommand _luanchcommand;//mvvm
static myCustomCommand.()
{
System.Windows.MessageBox.Show("from contructor"); // static consructor is called when static memeber is first accessed(non intanciated object)
InputGestureCollection gesturecollection = new InputGestureCollection();
gesturecollection.Add(new KeyGesture(Key.L,ModifierKeys.Control));//ctrl+L
_luanchcommand =new RoutedUICommand("Launch","Launch",typeof(myCustomCommand.),gesturecollection);
}
public static RoutedUICommand Launch
{
get
{
return _luanchcommand;
}
}
}
}
In the xaml of UserControl:
<UserControl x:Class="WpfApplication1.UserControl1"
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:CustomCommands="clr-namespace:WpfApplication1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.CommandBindings>
<CommandBinding Command="CustomCommands:myCustomCommand.Launch" Executed="CommandBinding_Executed">
</CommandBinding>
</UserControl.CommandBindings>
<Grid >
<TextBox Name="mytxt" Height="30" Width="60" Margin="50,50,50,50" ></TextBox>
<Button Name="b" Height="30" Width="60" Margin="109,152,109,78" Command="CustomCommands:ZenabUICommand.Launch"></Button>
</Grid>
Now in User control code
Handle command_executed
private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
mytxt.Text = "invoked on custom command";
}
}
}

Creating a single controller for multiple WPF Pages

I'm very new to WPF and a beginner in C#.NET. I'm currently making an application where there will be many pages and the trigger to change the page is hand gesture using Kinect SDK (the trigger method is not relevant for this question). Normally when a WPF file is created, there will be a similarly named .cs file attached to it, which acts somewhat like a controller. However, I need multiple WPF files/pages to be controlled only by a single controller .cs file. How do I achieve that? Thanks for viewing my question and your answer will be very appreciated :)
You probably want to write a class that contains your 'controller' code and reference it from your WPF UserControls / Pages.
In a new file:
public class MyController
{
public void DoThings(object parameter)
{
// stuff you want to do
}
}
and then inside your UserControl code-behind class:
public partial class MyWpfControl : UserControl
{
private MyController controller;
public MyWpfControl
{
this.controller = new MyController();
}
}
and finally, tie your events back to the controller's method:
private void OnGesture(object sender, EventArgs e)
{
// call the method on the controller, and pass whatever parameters you need...
this.controller.DoThings(e);
}
The code behind is really part of the view and isn't really analogous to a controller and generally there shouldn't be much code in them. Typically you would want most of your logic between your "View Model" which serves as an abstraction of the view and "Model" which serves as an abstraction of the business logic that your UI is interacting with.
In this light what I think you really want is a View Model(VM) that controls multiple views. This is a fairly typical scenario and the preferred method (IMO anyway) is to have a hierarchical view model that has a top level the application model and a number of sub VMs that represent different components within your UI, though you can bind everything to your top level VM if you really want to.
To do this we would first define our view model like so
public interface IGestureSink
{
void DoGesture();
}
public class MyControlVM : INotifyPropertyChanged, IGestureSink
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private ApplicationVM parent;
public MyControlVM(ApplicationVM parent)
{
this.Name = "my user control";
this.parent = parent;
parent.PropertyChanged += (s, o) => PropertyChanged(this, new PropertyChangedEventArgs("Visible"));
}
public String Name { get; set; }
public bool Visible { get { return parent.ControlVisible; } }
public void DoGesture()
{
parent.DoGesture();
}
}
public class ApplicationVM : INotifyPropertyChanged, IGestureSink
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public ApplicationVM()
{
this.ControlVM = new MyControlVM(this);
this.ControlVisible = false;
}
public MyControlVM ControlVM { get; private set; }
public bool ControlVisible {get; set;}
public void DoGesture()
{
this.ControlVisible = !this.ControlVisible;
PropertyChanged(this, new PropertyChangedEventArgs("ControlVisible"));
}
}
and then all we need to do is to build a user control
<UserControl x:Class="WpfApplication2.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="LightBlue">
<Label Content="{Binding Name}"/>
</Grid>
</UserControl>
and page
<Window xmlns:my="clr-namespace:WpfApplication2" x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</Window.Resources>
<Grid>
<my:MyControl Width="200" Height="200" x:Name="myUserControl" DataContext="{Binding ControlVM}" Visibility="{Binding Visible,Converter={StaticResource BooleanToVisibilityConverter}}"/>
<Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="222,262,0,0" Name="button1" VerticalAlignment="Top" Width="75" Click="button1_Click" />
</Grid>
</Window>
That use it. The only thing that we need in our code behind is a constructor that sets up the page VM and wiring from our button to the view model.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ApplicationVM();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
((IGestureSink)(this.DataContext)).DoGesture();
}
}
If you wanted to use a monolithic view model instead you would use this Instead of binding the DataContext to ControlVM:
<my:MyControl Width="200" Height="200" x:Name="myUserControl" DataContext="{Binding DataContext}" Visibility="{Binding ControlVisible,Converter={StaticResource BooleanToVisibilityConverter}}"/>

Categories