WPF Command Binding on Code Behind problem - c#

I am new to WPF and I have created a WPF Application. In that application, I have a UserControl that contain a button as below,
<UserControl.DataContext>
<local:AppViewModel/>
</UserControl.DataContext>
<Grid>
<Button x:Name="Btn_Contact" Command="{Binding BookVM.LoadContactsCommand}" Click="Btn_Contact_Click"/>
</Grid>
And My AppViewModel Class is as below
public AppViewModel()
{
var dataService = new JsonContactDataService();
BookVM = new BookViewModel(dataService);
CurrentView = BookVM;
}
My problem is I want this UserControl to run command of the Btn_Contact automatically when the UserControl is loaded instead of clicking the button. I have try to write the command binding on the UserControl code-behind but it does not worked.
public UserControlMemo()
{
InitializeComponent();
Btn_Contact.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
Btn_Contact.SetBinding(Button.CommandProperty, new Binding("BookVM.LoadContactsCommand"));
}

In its simplest form:
public UserControlMemo()
{
Loaded += OnLoaded;
InitializeComponent();
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
if (DataContext is AppViewModel viewModel)
viewModel.BookVM.LoadContactsCommand.Execute(null);
}

Related

Change view using a button click in WinUI 3

In WinUI 3 I want to change the view to a SecondaryView after a button click. The view change works flawlessly if I just add it to my code. But as soon as it happens in a Button Click function the app crashes. I am using the Template Studio for WinUI template to do this. The relative code is as follows:
MainPage.xaml:
<Grid x:Name="ContentArea">
<TextBlock Text="Main Page"/>
<Button Content="Press" Click="Button_Clicked"/>
</Grid>
MainPage.xaml.cs
private readonly INavigationService _navigationService;
public MainPage()
{
ViewModel = App.GetService<MainViewModel>();
InitializeComponent();
_navigationService.NavigateTo(typeof(SecondaryViewModel).FullName); // WORKS
}
private void Button_Clicked(object sender, RoutedEventArgs e)
{
_navigationService.NavigateTo(typeof(SecondaryViewModel).FullName); // DOESN'T WORK
}
The exception I get is
#if DEBUG && !DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION
UnhandledException += (sender, e) =>
{
if (global::System.Diagnostics.Debugger.IsAttached) global::System.Diagnostics.Debugger.Break();
};
#endif
This is all right from the template, barely changing anything. I tried it in my own code first before trying the template and got the same error. Is there any way to change the view on a button click?
You need to initialize the _navigationService.
public sealed partial class MainPage : Page
{
private readonly INavigationService _navigationService;
public MainViewModel ViewModel
{
get;
}
// Constructor
public MainPage()
{
ViewModel = App.GetService<MainViewModel>();
InitializeComponent();
_navigationService = App.GetService<INavigationService>(); // This line is missing.
_navigationService.NavigateTo(typeof(SecondaryViewModel).FullName);
}
private void Button_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
{
_navigationService.NavigateTo(typeof(SecondaryViewModel).FullName);
}
}

How do I access the content of the frame in different class? WPF

I wanted to change the frame's content to the Register.xaml page when the RegisterButton is click but it doesn't do it. How do I properly access the frame from the MainWindow?
Please help! I've been here long enough.
MainWindow.xaml
<Frame x:Name="MainFrame" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="3"
Padding="10" BorderBrush="DarkGray" BorderThickness="2"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainFrame.Content = new Navigation();
}
public void GoToRegister()
{
MainFrame.Content = new Register();
}
}
Navigation.xaml
<Button x:Name="RegisterButton" Grid.Row="0" Grid.Column="0" Background="LightSteelBlue" Content="Register" FontSize="24" Margin="10" Click="RegisterButton_Click"/>
Navigation.xaml.cs
public partial class Navigation : Page
{
public Navigation()
{
InitializeComponent();
}
private void RegisterButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show("success");
MainWindow Main = new MainWindow();
Main.MainFrame.Content = new Register();
}
}
In your Navigation you're creating a new MainWindow (which you don't display) and this is why it's not working. If you want to access MainWindow from underlying classes (I wouldn't do it, but it's a different story), you can, for example, create a public static method that takes a Page as an input parameter and sets it as the content for MainFrame:
In your MainWindow.xaml.cs
public static void SetMainFrameContent(Page page)
{
this.MainFrame.Content = page;
}
And then in your Navigation.xaml.cs button click event handler you call this method:
private void RegisterButton_Click(object sender, RoutedEventArgs e)
{
MainWindow.SetMainFrameContent(new Register());
MessageBox.Show("success");
}
What also may help is setting the data context of each of the windows, simply call this.DataContext = this; in the constructor.

How to send TextBox value from modal window to main window programmatically?

I'm trying to learn WPF MVVM I would need to understand how to update a textbox value via a modal window. Below the code, I wrote passes the value to the viewmodel but does not update the textbox. Thanks in advance
UserControl con il TextBox
<TextBox x:Name="Text01UC" Text="{Binding TextUC, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Width="200" Height="33"/>
UserControl Behind
namespace InvioDati
{
public partial class textbox : UserControl
{
public textbox()
{
InitializeComponent();
var vm = new ModelTextView();
this.DataContext = vm;
vm.Load();
}
private void Open_Click(object sender, RoutedEventArgs e)
{
MoadalWindow md = new MoadalWindow();
md.ShowDialog();
}
}
}
ModelTextView
namespace InvioDati
{
class ModelTextView : BaseViewModel
{
private ModelText dati = new ModelText();
public string TextUC
{
get => dati.TextVal;
set
{
dati.TextVal = value;
OnPropertyChanged();
}
}
public void Load() {
TextUC = "GoodMorning";
}
public void Ricevi(string valore)
{
TextUC = valore;
}
}
}
ModalWindow Code behind
namespace InvioDati
{
public partial class MoadalWindow : Window
{
public MoadalWindow()
{
InitializeComponent();
}
private void Test_Click(object sender, RoutedEventArgs e)
{
ModelTextView nd = new ModelTextView();
nd.Ricevi(Send.Text);
this.Close();
}
}
}
Set the DataContext of the ModalWindow to the same instance of ModelTextView in textbox.xaml.cs:
private void Open_Click(object sender, RoutedEventArgs e)
{
MoadalWindow md = new MoadalWindow();
md.DataContext = this.DataContext;
md.ShowDialog();
}
You can then either bind directly to the TextUC property or do the following in ModalWindow.xaml.cs:
private void Test_Click(object sender, RoutedEventArgs e)
{
ModelTextView nd = DataContext as ModelTextView;
nd.Ricevi(Send.Text);
this.Close();
}
You must use a mediator in order not to break mvvm here.
Check https://en.wikipedia.org/wiki/Mediator_pattern#C#
1b. Add Observer pattern to create notifications for value changes.
Dialogs are evil within MVVM, usually you won't need them. What you want is an overlaying View, which can be Data bound in any way as there is no break in the visual tree
If you want to use "dialogs", implement a DialogService to do so.
Edit: here is a draft on how you create something "popup" like in the most simple way:
<UserControl>
<Grid>
<!--Invert visability of all controls below via binding-->
<YourMainControl/>
<Rect Fill="Black" Opacity=".5 Visibility="Hidden"/>
<YourSubControl Visibility="Hidden"/>
</Grid>
</UserControl>

How to instance a child Window in WPF that takes parameters from the main Window, but respects MVVM

I have implemented something violating the MVVM pattern, and I wondered if there was a MVVM way of doing this.
I have a Window MainWindow, its DataContext is bound to a class called ViewModel which implements INotifyPropertyChanged.
I also implemented a Window ChildWindow which appears in a "Dialog" style when a button is clicked, using a RelayCommand. The DataContext of ChildWindow also binds to ViewModel. This Window is used to fill the details of a new list Item. I pass the View as a CommandParameter to the ViewModel, so that the ChildWindow can be centered in comparison to the MainWindow. This is not MVVM, and I would like to change this.
First, I implemented this in a non-MVVM way:
Here is my XAML for the button in MainWindow which opens the ChildWindow:
<Button Name="BtnInsert" Width="50" Margin="10" Command="{Binding OpenChildWindowCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}">Add</Button>
Here is my simplified XAML for the ChildWindow:
<Window x:Class="HWE_Einteilen_Prototype.View.ListItemWindow"
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:HWE_Einteilen_Prototype.View"
mc:Ignorable="d"
Title="test" Height="400" Width="400">
<TextBox Width="50" Text="{Binding CurrentListItem.Id}" ></TextBox>
</Window>
And here is my (simplified) ViewModel Class:
public class ViewModel : INotifyPropertyChanged
{
private DataContext _ctx;
private ListItem _currentListItem;
private ObservableCollection<listItem> _listItems;
private ListItemWindow _listItemWindow;
private ICommand _openListItemWindowCommand;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<ListItem> ListItems
{
get { return _listItems; }
set
{
_listItems = value;
OnPropertyChanged();
}
}
public ListItem CurrentListItem
{
get { return _currentListItem; }
set
{
_currentListItem = value;
OnPropertyChanged();
}
}
public ICommand OpenListItemWindowCommand
{
get { return _openListItemWindowCommand; }
set
{
_openListItemWindowCommand = value;
OnPropertyChanged();
}
}
public ViewModel()
{
OpenListItemWindowCommand = new RelayCommand(this.OpenNewListItemWindow, this.CanOpenListItemWindow);
}
private void OpenNewListItemWindow(object parameter)
{
CurrentListItem = new listItem(){Id = "testId"};
_listItemWindow = new StListItemWindow(){DataContext = this};
_listItemWindow.Owner = (MainWindow)parameter;
_listItemWindow.WindowStartupLocation = WindowStartupLocation.CenterOwner;
_listItemWindow.Closing += OnStListItemWindowClosing;
_listItemWindow.Show();
}
private bool CanOpenListItemWindow(object parameter)
{
return true;
}
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
What I have tried:
I have tried implementing a Behavior (from system.windows.interactivity) for the button opening the child window, so that it creates a new Window and does all the centering and owner stuff, and leaving only CurrentListItem = new listItem(){Id = "testId"}; in the command method. However, in this case binding to CurrentListItem in the ChildWindow throws an exception.
XAML Code for the MainWindow Button:
<Button Name="BtnInsert" Width="50" Margin="10" Command="{Binding OpenListItemWindowCommand}" Content="Add">
<i:Interaction.Behaviors>
<behaviors:BehButtonNewWindow></behaviors:BehButtonNewWindow>
</i:Interaction.Behaviors>
</Button>
Behavior Code:
class BehButtonNewWindow : Behavior<Button>
{
private StListItemWindow _ListItemWindow;
protected override void OnAttached()
{
AssociatedObject.Click += OnClickHandler;
}
protected override void OnDetaching()
{
AssociatedObject.Click -= OnClickHandler;
}
private void OnClickHandler(object sender, RoutedEventArgs routedEventArgs)
{
if (sender is Button button)
{
var win = Window.GetWindow(button);
if (win != null)
{
_ListItemWindow = new ListItemWindow
{
DataContext = win.DataContext,
Owner = win,
WindowStartupLocation = WindowStartupLocation.CenterOwner
};
_ListItemWindow.Show();
}
}
}
}
Code of Command Execute Method from ViewModel:
private void OpenNewStListItemWindow(object parameter)
{
CurrentListItem = new ListItem(){Id = "testId"};
}
What am I doing wrong?
Credit for this answer goes to Will (see comments)
On handling the window opening:
Opening a window is a UI concern. Simply handle the button click in the codebehind, construct a new window and stick the current VM in it. MVVM != no codebehind.
On handling vm code:
[...] If you mean that last little bit of code at the bottom, make it public and have the window call it before opening the new window. The UI is perfectly fine knowing about your view models. They're designed to display their state and bind to their properties.
Thanks for your help!

Access the main window datacontext from a new created window

In my MainWindow I create a new instance of a class containing different settings. After setting the parameters of the class, I set the datacontext = to that class.
public partial class MainWindow : Window
{
private MeasConSettings mMeasConSettings = new MeasConSettings();
public MainWindow()
{
InitializeComponent();
DataContext = mMeasConSettings;
}
private void MenuComm_Click(object sender, RoutedEventArgs e)
{// See code below}
}
Now I also have a function to open a new window, this window contains a textbox who's text should be bound to the datacontext of the MainWindow.
private void MenuComm_Click(object sender, RoutedEventArgs e)
{
FrmSettings newWindow = new FrmSettings();
newWindow.DataContext = mMeasConSettings;
newWindow.TxtComm.Text = mMeasConSettings.CommSettings;
newWindow.Show();
}
This code fills in the textbox from the newWindow with the right content, BUT it does not get bound propery since the datacontext does not get updated after changing the text in the textbox (TxtComm in the new created window).
An example of the XAML code for the textbox:
<TextBox Grid.Row="1" Grid.Column="3" Margin="2,0" Name="TxtComm" DataContext="{Binding Path=CommSettings, UpdateSourceTrigger=PropertyChanged}" />
"CommSettings" is a member of the MeasConsettings class
public class MeasConSettings
{
private string mCommSettings;
public string CommSettings
{
get
{
return mCommSettings;
}
set
{
mCommSettings = value;
}
}
public MeasConSettings()
{
CommSettings = "Com5:19200,8,n,1";
}
}
My problem is how can I adjust the value mMeasConSettings.CommSettings (defined in my MainWindow) in my newWindow (Which is created after pressing a button), If I change the textbox value in my newWindow, the value stored in mMeasConSettings.CommSettings should also be changed.
PS: I'm new to WPF so any advice is welcome!
As I wrote in the comment, you need to bind the Text property of your TextBox to the property of the DataContext which you want to update. Your XAML should thus be something like:
<TextBox ... Text="{Binding CommSettings, Mode=TwoWay}" />
Note that I am binding the Text property of the TextBox to the property CommSettings of your DataContext. And your C#-code for the click event should be:
private void MenuComm_Click(object sender, RoutedEventArgs e)
{
FrmSettings newWindow = new FrmSettings();
newWindow.DataContext = mMeasConSettings;
newWindow.Show();
}
We only need to set the DataContext here. Note that the DataContext is passed along to child elements, so the TextBox will have the same DataContext as its parent unless specifically set to something else.
use static property:
class Demo
{
public static string SomeSettings {get;set;}
private onLoad()
{
SomeSettings=... //Init here
}
}
In other file:
Demo.SomeSettings=....

Categories