Triggering code-behind events with InputBindings - c#

Got stuck while adding key-shortcuts to an MVVM app. Searched for a solution but could not find an example where triggering a command was hindered by the DataContext. This leads me to think I'm perhaps trying to do this the wrong way.
See the following example.
Window XAML
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1" x:Class="WpfApplication1.MainWindow"
Title="Stackoverflow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Window.InputBindings>
<KeyBinding Key="F" Modifiers="Control"/>
</Window.InputBindings>
<Grid>
<Button Content="Interface Action" Click="ButtonBase_OnClick"/>
</Grid>
</Window>
Window Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// Do Work
}
}
Trying to link the input binding to the button, what is the best design choice? These are the options I can think of:
Setup code-behind to be the Data-Context, create a code-behind property ViewModel and define the path deeper in the XAML tree
Write a key-press event in the code-behind and trigger the event from there
Both options seems to be either aesthetically wrong or cause a lot of extra code to be written. What would be the best option from an MVVM point of view?

Related

C#/AvaloniaUI - Click a button and change Text

I am very new to AvaloniaUI.
I am really struggling to change a text when I click a button.
Here is my code:
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ReadyForWar_Launcher.MainWindow"
Title="ReadyForWar_Launcher">
<StackPanel>
<TextBlock Name="TestBlock">Show my text here!</TextBlock>
<Button Command="{Binding RunTheThing}" CommandParameter="Hello World">Change the Text!</Button>
</StackPanel>
</Window>
Here is my MainWindow.xaml.cs:
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace ReadyForWar_Launcher
{
public class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public void RunTheThing()
{
}
}
}
Inside RunTheThing I don't know how can I select the TextBlock with Name="TestBlock" and change the text to "Hello World".
Can you please help me out on this ?
There are two approaches, the recommended one and straightforward one.
Recommended: Use MVVM pattern. Create a view model with ButtonTextProperty and RunTheThing command, make the command to change the property, assign that model to the DataContext and bind your button text and command to view model properties. The MVVM approach is basically the same as in WPF, so you can use documentation and tutorials from there (that applies to most of the Avalonia, BTW). For example, here is a good one (not advertising, 4th link from google).
Straightforward (aka winforms-way): add x:Name="MyButton" to your button and use this.FindControl<Button>("MyButton") after calling AvaloniaXamlLoader.Load(this);. This will give you a Button reference that you can manipulate from code. Instead of using commands, you can just subscribe to the click handler directly from codebehind, add public void MyButton_OnClick(object sender, RoutedEventArgs args){} to your MainWindow class and add replace Command and CommandParameter with Click="MyButton_OnClick". That way button click will trigger your event handler.
Note, that the second approach doesn't scale well with the application size and suffers from code complexity when handling lists.

How to create child window by the context menu of page in wpf?

I have a context menu what is triggered by each list item of listbox.
And, I want to create child window when I select a context menu as below:
xaml
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Width="150" Orientation="Vertical" Margin="15, 5, 15, 5">
<StackPanel.ContextMenu>
<ContextMenu FontSize="16">
<MenuItem Header="{x:Static localRes:Resources.ID_STRING_SETTING}" Margin="5" Command="Setting_Click"/>
</ContextMenu>
It is already a sub page of main window.
So, I can't find a way how to set MainWindow instance as the owner of new window.
Behind Code
private void Setting_Click(object sender, RoutedEventArgs e)
{
SettingWindow SettingWindow = new SettingWindow();
SettingWindow.Owner = /* I do not know how to do */
SettingWindow.Show();
}
If your click command handler is in the code behind for your main window, then you need to set
deviceSettingWindow.Owner = this;
https://learn.microsoft.com/en-us/dotnet/api/system.windows.window.owner?view=netframework-4.8
Here's a small example. It includes a button with a handler whose code is in the code behind -
<Window x:Class="MainWindow.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ChildWindow"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Content="Button" HorizontalAlignment="Left" Margin="114,137,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
</Window>
CodeBehind:
using System.Windows;
namespace MainWindow
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var childWindow = new ChildWindow.Window1();
childWindow.Owner = this;
childWindow.Show();
}
}
}
Child Window - just an empty window
<Window x:Class="ChildWindow.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ChildWindow"
mc:Ignorable="d"
Title="Window1" Height="450" Width="800">
<Grid>
</Grid>
</Window>
Child Window Code Behind
using System.Windows;
namespace ChildWindow
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
}
}
In my example, Since you are in the code behind for the MainWindow, this is a reference to the MainWindow, and setting 'childWindow.Owner = this' is setting the childWindow's owner to the MainWindow which is what I believe you want.
One thing that is a little confusing to me is that you are using a Command and a reference to an event handler in the code behind. I'm pretty sure that's not going to work. Commands need to Bind to an ICommand reference - you'll have to implement your own ICommand class, or use one from MVVM Light or another WPF MVVM Framework. Once you've got that you can pass a reference from the parent window through the Command as a CommandParameter. For an example of how to do this, see passing the current Window as a CommandParameter
If you are using an event handler on a control, then that can bind to the event handler implementation in the code behind like in my example. You need to choose one or the other.
If you're able to provide more details on how you're setup, it would make it easier for me to provide input on which way you need to go.

WPF binding from code-behind to Grid

I am trying to view the coordinates of a mouse click on a WPF screen with no luck. I have a rudimentary grid layout with a Textblock that will display these coordinates. I have bound values from the xaml to a code-behind before but am not sure if this direction is possible. My xaml is as follows
<Window x:Class="MouseUpExample.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"
Name="MyWindow">
<Grid>
<TextBlock Text="{Binding Path=GetMouseCoordinates}"/>
</Grid>
</Window>
and my code-behind is as follows
using System.Windows;
using System.Windows.Input;
using System.Windows.Shapes;
namespace MouseUpExample
{
public partial class MainWindow : Window
{
Point currentPoint = new Point();
public MainWindow()
{
InitializeComponent();
}
private string GetMouseCoordinates(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (e.ButtonState == MouseButtonState.Pressed)
{
currentPoint = e.GetPosition(this);
return currentPoint.ToString();
}
return "error";
}
}
any help would be greatly appreciated, thank you.
You've got multiple problems here.
Problem #1 is that you can't bind to a method. It needs to be a property and preferably either a DependencyProperty or one that takes part in the INotifyPropertyChanged interface.
Problem #2 is that binding works with the DataContext by default, but you're not setting the DataContext of your TextBlock, either explicitly or implicitly.
Problem #3 is that this doesn't really make sense. Is GetMouseCoordinates an event handler for something? You probably would want to split out the event handler and the property.
I'd suggest that you go read up on DataBinding in WPF and then give it another shot.
To be able to bind you'd have to set the DataContext to a class that implements INotifyPropertyChanged and bind to its properties. As you don't have it defined I'd set that text directly in the view.
<Window x:Class="MouseUpExample.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"
Name="MyWindow" MouseDown="MainWindow_OnMouseDown">
<Grid>
<TextBlock Name="MyTextBlock"/>
</Grid>
</Window>
And in the code behind:
private void MainWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
{
MyTextBlock.Text = e.GetPosition(this).ToString();
}

Adding UserControl from one window to the MainWindow

I have a Mainwindow and a groupbox inside it.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication1.MainWindow"
Title="MainWindow"
Height="600" Width="800">
<Grid x:Name="MainGrid">
<GroupBox Header="Diagram Design" Name="gbDiagDesign">
</GroupBox>
</Grid>
</Window>
A simple UserControl
<UserControl x:Class="WpfApplication1.Controls.EntityControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="100">
<Grid>
<Button x:Name="btn_show" Content="show me" />
</Grid>
</UserControl>
and another window with an OK button in it,
the question is how do I add the UserControl to the Groupbox in the MainWindow after I press the OK button.
public partial class NewEntity
{
public NewEntity()
{
InitializeComponent();
}
private void OK_Click(object sender, RoutedEventArgs e)
{
EntityControl entcon = new EntityControl();
**MainWindow.gbDiagDesign.Children.Add(**
}
the last row gives me an error, "an object reference is required for the nonstatic field.."
You can access MainWindow using Application.Current.MainWindow but it will return instance of Window class. Typecasting is required to convert it to actual class instance i.e. MainWindow.
This should work:
((MainWindow)Application.Current.MainWindow).gbDiagDesign.Add(entcon);
WPF is meant to be programmed using the MVVM pattern. If you do it another way, you will have to fight WPF all along instead of using it's great power. If you insist on doing it the other way, at least make the compiler happy:
MainWindow is a instance variable of your application class. You are not inside your application class, so you need an instance of your application class first to access MainWindow. You will also need to cast it to your Window type.

Caliburn Micro and ModernUI Examples/Tutorials

does anyone have an example or tutorial on how to use Caliburn Micro together with ModernUi (https://mui.codeplex.com)?
Ok so I had a quick mess about with it and a look on the Mui forums and this seems to be the best approach:
Since the window loads content from URLs you need to take a view-first approach, and then locate the appropriate VM and bind the two.
The best way to do this appears to be via the ContentLoader class which is used to load the content into the ModernWindow when it is requested. You can just subclass DefaultContentLoader and provide the necessary CM magic to bind up loaded items:
public class ModernContentLoader : DefaultContentLoader
{
protected override object LoadContent(Uri uri)
{
var content = base.LoadContent(uri);
if (content == null)
return null;
// Locate the right viewmodel for this view
var vm = Caliburn.Micro.ViewModelLocator.LocateForView(content);
if (vm == null)
return content;
// Bind it up with CM magic
if (content is DependencyObject)
{
Caliburn.Micro.ViewModelBinder.Bind(vm, content as DependencyObject, null);
}
return content;
}
}
Your CM bootstrapper should just bootstrap a ModernWindow viewmodel which is backed by a ModernWindow based view (CM tries to use EnsureWindow which creates a new basic WPF Window class, unless of course your control already inherits from Window which ModernWindow does. If you need all dialogs and popups to be MUI you might need to reimplement WindowManager):
public class Bootstrapper : Bootstrapper<ModernWindowViewModel>
{
}
Which can be a conductor (OneActive) and looks like this:
public class ModernWindowViewModel : Conductor<IScreen>.Collection.OneActive
{
}
And XAML for the view is
ModernWindowView.xaml
<mui:ModernWindow x:Class="WpfApplication4.ViewModels.ModernWindowView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mui="http://firstfloorsoftware.com/ModernUI"
Title="ModernWindowView" Height="300" Width="300" ContentLoader="{StaticResource ModernContentLoader}">
<mui:ModernWindow.MenuLinkGroups>
<mui:LinkGroupCollection>
<mui:LinkGroup GroupName="Hello" DisplayName="Hello">
<mui:LinkGroup.Links>
<mui:Link Source="/ViewModels/ChildView.xaml" DisplayName="Click me"></mui:Link>
</mui:LinkGroup.Links>
</mui:LinkGroup>
</mui:LinkGroupCollection>
</mui:ModernWindow.MenuLinkGroups>
</mui:ModernWindow>
Obviously you need to make the loader a resource too:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.xaml" />
<ResourceDictionary Source="/FirstFloor.ModernUI;component/Assets/ModernUI.Dark.xaml"/>
<ResourceDictionary>
<framework:ModernContentLoader x:Key="ModernContentLoader"></framework:ModernContentLoader>
<wpfApplication4:Bootstrapper x:Key="Bootstrapper"></wpfApplication4:Bootstrapper>
</ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
Here's the ChildViewModel I'm using as a test:
public class ChildViewModel : Conductor<IScreen>
{
public void ClickMe()
{
MessageBox.Show("Hello");
}
}
And the XAML for that (just a button)
<UserControl x:Class="WpfApplication4.ViewModels.ChildView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="350" Width="525">
<Grid>
<StackPanel>
<TextBlock >Hello World</TextBlock>
<Button x:Name="ClickMe" Width="140" Height="50">Hello World</Button>
</StackPanel>
</Grid>
</UserControl>
And the proof of concept:
I create a very, very simple sample of chat app using Modern UI for WPF, Caliburn Micro and MEF.
https://github.com/gblmarquez/mui-sample-chat
I hope it helps

Categories