How do I close a window from its user control view model? - c#

I have a window which hosts various UserControl's as pages. Is it possible to close the window which I have no reference to from within the usercontrol's datacontext?
Simplified details:
SetupWindow
public SetupWindow()
{
InitializeComponent();
Switcher.SetupWindow = this;
Switcher.Switch(new SetupStart());
}
public void Navigate(UserControl nextPage)
{
this.Content = nextPage;
}
SetupStart UserControl
<UserControl x:Class="...">
<UserControl.DataContext>
<local:SetupStartViewModel/>
</UserControl.DataContext>
<Grid>
<Button Content="Continue" Command="{Binding ContinueCommand}"/>
</Grid>
</UserControl>
SetupStartViewModel
public SetupStartViewModel()
{
}
private bool canContinueCommandExecute() { return true; }
private void continueCommandExectue()
{
Switcher.Switch(new SetupFinish());
}
public ICommand ContinueCommand
{
get { return new RelayCommand(continueCommandExectue, canContinueCommandExecute); }
}

I managed to find a solution from an answer here: How to bind Close command to a button
View-Model:
public ICommand CloseCommand
{
get { return new RelayCommand<object>((o) => ((Window)o).Close(), (o) => true); }
}
View:
<Button Command="{Binding CloseCommand}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Content="Close"/>

I do this by having a RequestClose event in my ViewModel that it can raise when it wants the view to close.
This is then hooked up to the window's Close() command by the code that creates the window. eg
var window = new Window();
var viewModel = new MyViewModel();
window.Content = viewModel;
viewModel.RequestClose += window.Close;
window.Show()
This way all the stuff to do with window creation is handled in one place. Neither the view, or the viewmodel know about windows.

Inside your user control you can find a reference to the window that's hosting it with a static method on the Window class.
var targetWindow = Window.GetWindow(this);
targetWindow.Close();
Edit:
If you have no reference to the user control that the data context is being used in you don't have a huge amount of options, if there is just 1 application window you can get away with
Application.Current.MainWindow.Close()
If there are many windows in your application and the one you want to close is in focus you could find that with something like
public Window GetFocusWindow()
{
Window results = null;
for (int i = 0; i < Application.Current.Windows.Count; i ++)
if (Application.Current.Windows[i].IsFocused)
{
results = Application.Current.Windows[i];
break;
}
return results;
}
Finally I guess (though this is pretty out there) you could loop through the applications window classes, checking the data context of every object in the visual tree until you find the reference you're after, the window can be closed from there.

Related

Keep a MenuItem's IsChecked property synced with a bool

I have a simple WPF Application that has a menu on top. I want to add an option to make the main window to stay on top of other windows.
I created a bool named setTopMost in Property > Settings tab for users to save this setting. So, the setting will be remembered even after the app is terminated.
Everything is working as intended, I can click on the option or use the shortcut of Ctrl+T to make the window to stay on top, but I cannot get a check mark to appear next to the option when the window is on top of other windows.
I've read several articles regarding binding IsChecked to a bool, but I could not solve this problem on my own.
Here are my codes.
MainWindow.xaml
<Window.InputBindings>
<KeyBinding Gesture="Ctrl+T" Command="{Binding TopMostCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" />
</Window.InputBindings>
<MenuItem Header="_Options">
<MenuItem x:Name="Menu_AlwaysOnTop" Header="Always On _Top" IsCheckable="True" IsChecked="{Binding isTopMost}" Command="{Binding TopMostCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}" InputGestureText="Ctrl+T" />
</MenuItem>
MainWindow.xaml.cs
namespace WPF_Practice
{
public partial class MainWindow : Window
{
public bool isTopMost;
public MainWindow()
{
InitializeComponent();
DataContext = new PracticeDataContext();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
isTopMost = Properties.Settings.Default.setTopMost;
Topmost = Properties.Settings.Default.setTopMost;
}
}
public class PracticeDataContext
{
public ICommand TopMostCommand { get; } = new TopMostCommand();
}
public class TopMostCommand : ICommand
{
public void Execute(object parameter)
{
var TopMostClass = new MainWindow();
TopMostClass.WindowTopMost();
}
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
}
}
Please mind that I am doing this as a hobby and quite new to this.
The main reason why your MenuItem isn't updating properly is because you set the DataContext of the Window to PracticeDataContext.
public MainWindow()
{
InitializeComponent();
DataContext = new PracticeDataContext(); <--
}
This means that your bindings in MainWindow.xaml are going to be looking for properties in PracticeDataContext.
In this case you would want to have an IsTopMost property in your PracticeDataContext class in order for the binding to work.
Since IsTopMost isn't set until the Loaded event handler fires, you should implement INotifyPropertyChanged in your PracticeDataContext class so that your IsTopMost binding will get notified when it is set from settings.
A quick search on INotifyPropertyChanged will show you lots of examples. It's pretty easy.

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.

Async binding to SelectedItem in TabControl WPF issues

I have a panel with tabs. My view model for this panel contains ObservableCollection of view models for tabs, and a property for selected tab.
When some action requests to focus a tab, or a new tab is created, I change Selected and tab selection changes properly, well almost, because the content is valid, but all headers look like nothing is selected.
I found a solution that says to add IsAsync=True to my binding. This solved the problem but added a bunch of new issues.
First thing is that when I run program in debug mode, adding tabs with buttons works ok, tabs get switched and selected properly but when I try to click a tab to select it I get exception
The calling thread cannot access this object because a different thread owns it.
it is thrown while setting property representing currently selected tab:
private Tab selected;
public Tab Selected
{
get { return Selected; }
set { SetProperty(ref Selected, value); } // <<< here (I use prism BindableBase)
}
Other problem is that when I quickly switch tabs, it can come to a situation where I have Tab1 selected but it shows content of Tab2, switching tabs couple more times gets things back to work.
My question is, how can I solve this, i.e. have my tab headers selected (kind of highlighted) when Selected is changed, without having issues that assing IsAsync causes.
Edit
Here is the code that allows to reproduce issues. It uses prism 6.1.0
MainWindow.xaml
<Window x:Class="WpfApplication1.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:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<StackPanel DockPanel.Dock="Top"
Orientation="Horizontal"
Margin="0,5"
Height="25">
<Button
Command="{Binding AddNewTabCommand}"
Content="New Tab"
Padding="10,0"/>
<Button
Command="{Binding OtherCommand}"
Content="Do nothing"
Padding="10,0"/>
</StackPanel>
<TabControl
SelectedItem="{Binding Selected, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, IsAsync=True}" <!--remove IsAsync to break tab header selecting-->
ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" Margin="5"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}"/>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new TabGroup();
}
}
Tab.cs
public class Tab : BindableBase
{
public Tab(string name, string text)
{
this.name = name;
this.text = text;
}
private string name;
public string Name
{
get { return name; }
set { SetProperty(ref name, value); }
}
private string text;
public string Text
{
get { return text; }
set { SetProperty(ref text, value); }
}
}
TabGroup.cs
public class TabGroup : BindableBase
{
private Random random;
public TabGroup()
{
this.random = new Random();
this.addNewTabCommand = new Lazy<DelegateCommand>(() => new DelegateCommand(AddNewTab, () => true));
this.otherCommand = new Lazy<DelegateCommand>(() => new DelegateCommand(Method, () => Selected != null).ObservesProperty(() => Selected));
Tabs.CollectionChanged += TabsChanged;
}
private void Method()
{
}
private void TabsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var newItems = e.NewItems?.Cast<Tab>().ToList();
if (newItems?.Any() == true)
{
Selected = newItems.Last();
}
}
private void AddNewTab()
{
Tabs.Add(new Tab(GetNextName(), GetRandomContent()));
}
private string GetRandomContent()
{
return random.Next().ToString();
}
private int num = 0;
private string GetNextName() => $"{num++}";
private Tab selected;
public Tab Selected
{
get { return selected; }
set { SetProperty(ref selected, value); }
}
public ObservableCollection<Tab> Tabs { get; } = new ObservableCollection<Tab>();
private readonly Lazy<DelegateCommand> addNewTabCommand;
public DelegateCommand AddNewTabCommand => addNewTabCommand.Value;
private readonly Lazy<DelegateCommand> otherCommand;
public DelegateCommand OtherCommand => otherCommand.Value;
}
Preparing this let me figure where does the exception come from. It is because the OtherCommand observes selected property. I still don't know how to make it right. Most important for me is to get tabs to be selected when they should be and so that selected tab won't desynchronize with what tab control shows.
Here is a github repo with this code
https://github.com/lukaszwawrzyk/TabIssue
I'll focus on your original problem, without the async part.
The reason why the tabs are not properly selected when adding a new tab is because you set the Selected value in the CollectionChanged event handler. Raising an event causes sequential invocation of handlers in order in which they were added. Since you add your handler in the constructor, it will always be the first one to be invoked, and what's important, it will always be invoked before the one that updates the TabControl. So when you set the Selected property in your handler, TabControl doesn't yet "know" that there's such a tab in the collection. More precisely, the header container for the tab is not yet generated, and it cannot be marked as selected (which causes the visual effect you're missing), moreover, it won't be when it's finally generated. TabControl.SelectedItem is still updated, so you see the content of the tab, but it also causes header container previously marked as selected to be unmarked, and you eventually end up with no tab visibly selected.
Depending on your needs, there are several ways to solve this problem. If the only way of adding new tabs is through the AddNewTabCommand, you could just modify the AddNewTab method:
private void AddNewTab()
{
var tab = new Tab(GetNextName(), GetRandomContent());
Tabs.Add(tab);
Selected = tab;
}
In this case you should not set the Selected value in the CollectionChanged handler, because it will prevent PropertyChanged from being raised at the right time.
If AddNewTabCommand is not the only way of adding tabs, what I usually do is to create a dedicated collection which would do the required logic (this class is nested in TabGroup):
private class TabsCollection : ObservableCollection<Tab>
{
public TabsCollection(TabGroup owner)
{
this.owner = owner;
}
private TabGroup owner;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e); //this will update the TabControl
var newItems = e.NewItems?.Cast<Tab>()?.ToList();
if (newItems?.Any() == true)
owner.Selected = newItems.Last();
}
}
Then simply instantiate the collection in the TabGroup constructor:
Tabs = new TabsCollection(this);
If this scenario appears in various places and you don't like repeating your code, you could create a reusable collection class:
public class MyObservableCollection<T> : ObservableCollection<T>
{
public event NotifyCollectionChangedEventHandler AfterCollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
base.OnCollectionChanged(e);
AfterCollectionChanged?.Invoke(this, e);
}
}
and then subscribe to AfterCollectionChanged whenever you need to be sure that all CollectionChanged subscribers have been notified.
When you get the error "The calling thread cannot access this object because a different thread owns it." this means that you are trying to access an object on another concurrent thread. To show you how to resolve this i want to give an example. First you have to find every runtime objects, like listboxes and listviews and such. (Basically GUI controls). They run on a GUI thread. When you try to run them on another thread forexample a backgroundworker or an task thread, the error appears. So this is what you want to do:
//Lets say i got a listBox i want to update in realtime
//this method is for the purpose of the example running async(background)
public void method(){
//get data to add to listBox1;
//listBox1.Items.Add(item); <-- gives the error
//what you want to do:
Invoke(new MethodInvoker(delegate { listBox1.Items.Add(item); }));
//This invokes another thread, that we can use to access the listBox1 on.
//And this should work
}
Hope it helps.

Using commands to fire usercontrol method

I need some help and I hope you can help me. I have a complex usercontrol with a method that changes the color of all elements inside. When I try to connect it with a method stub in the MainWindow-Code-behind, I can fire it up easily. I want to use MVVM in the future so now I want to connect it to a button in the main window through commands.
So here's my ViewModel and my MainWindow.cs
public class ViewModel
{
public DelegateCommands TestCommand { get; private set; }
public ViewModel()
{
TestCommand = new DelegateCommands(OnExecute, CanExecute);
}
bool CanExecute(object parameter)
{
return true;
}
void OnExecute()
{
//testUC.NewColor(); HERE I WANT TO START THE UC-METHOD
}
}
public partial class MainWindow : Window
{
ViewModel _ViewModel = null;
public plate tplate;
public MainWindow()
{
InitializeComponent();
_ViewModel = new ViewModel();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
tplate = new plate();
}
}
On my MainWindow-View I have a simple button and the usercontrol.
<exte:plate x:Name="testUC" Grid.Column="1"/>
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="43,247,0,0" Command="{Binding TestCommand}"/>
I want to start the UC-Method in the OnExecute()-Method but I'm not able to select the "testUC" because it's not available in this context.
Is there an easy way to start the UC-Methods through command bindings?
Thanks for any help.
Timo
How to solve you're binding problem. First, most bindings are related to the most specific DataContext. Means, you have to set your control as such. E.g.
public class MySpecialButton : Button
{
public MySpecialButton()
{
DataContext = this; // there are other possibilties, but this is the easiest one
}
}
With this you can bind every command implemented in MySpecialButton.
Another possibility is to use bindings with relative source. E.g.
<Button Command="{Binding TheCmd, RelativeSource={AncestorType={x:Type MySpecialButton}}}" />
You could even declare the DataContext with the method in the example above.
Hope this helps you.
Ok, I tried it the latter way you described ("Button" is the Button I want to use to trigger the method, "NewPlate" is the method-name and exte:plate is the customcontrol-type):
<Button Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" Margin="43,247,0,0" Command="{Binding NewPlate, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type exte:plate}}}" />
But again, nothing happens.
To understand your first suggestion correctly, I have to declare my Custom-Control as a Button instead of a user control? I do not understand, where I have to put the datacontext stuff. I'm just using a standard wpf-button to trigger the custom-control-method. I do not have any class for this button where I can declare a datacontext.

Control Databinding to View not ViewModel in MVVM scenario

I've been working with the MVVM model for a week or so now and I think I have a handle on what should go where now. Note the "think" in that.
I have a single ViewModel that my view (MainWindow) binds to
_ViewModel = new MainViewModel();
this.DataContext = _ViewModel;
I have a few ICommands that do work within the ViewModel and subsequently the Model, which I'm fine with.
Now I initiate a few windows from my View (MainWindow) which I do in codebehind, as it's all purely view related stuff. I am trying to replicate the ICommand setup I have in the ViewModel in the View to simplify my life, or so I thought. I have the following commands set-up:
public ICommand comInitialiseWindows { get; private set; }
private bool _windowsactive = false;
public bool WindowsActive
{
get { return _windowsactive; }
set { SetProperty(ref _windowsactive, value); }
}
public bool comInitialiseWindows_CAN()
{
return !_windowsactive;
}
private void comInitialiseWindows_DO()
{
... Code to do the window creation, etc.
}
I have this relay command in the MainWindow code:
comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
If I put this in the ViewModel it works a treat apart from the window creation stuff, but as it's View related I'm not surprised.
So the problem is the code doesn't run when I click the button. I'm guessing that the XAML is bound to the ViewModel, but I can't figure a way around this without setting the Binding for each button to the MainWindow in codebehind. I had assumed that the following would work, but it doesn't:
<Button x:Name="ribbutLayoutWindows"
Command="{Binding local:comInitialiseWindows}"
IsEnabled="{Binding local:comInitialiseWindows_CAN}"/>
I'm pretty sure I'm just not getting something somewhere. Or I'm trying to overcomplicate matters where a normal button click would have sufficed as it's View only.
Any suggestions?
There are two possibilities:
Through the ViewModel:
You could expose a Property on your ViewModel:
class MainViewModel
{
ICommand comInitialiseWindows {get; set;}
}
And in your MainWindow:
MainViewModel vm = this.DataContext as MainViewModel;
vm.comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
XAML:
<Button x:Name="ribbutLayoutWindows" Command="{Binding comInitialiseWindows}" />
Note: you don't need to bind the IsEnabled property. WPF will handle that for you and automatically call into the CanExecute-method of your ICommand.
Through a DependencyProperty
Declare this dependecyProperty in your code-behind:
public ICommand comInitialiseWindows
{
get { return (ICommand)GetValue(MyPropertyProperty); }
set { SetValue(MyPropertyProperty, value); }
}
public static readonly DependencyProperty comInitialiseWindowsProperty =
DependencyProperty.Register("comInitialiseWindows", typeof(ICommand), typeof(MainWindow), new PropertyMetadata(null));
Assign a value in the code-behind:
comInitialiseWindows = new RelayCommand(() => comInitialiseWindows_DO(), comInitialiseWindows_CAN);
After that, you need to break out of your data-context in the XAML. First of all, give your Page a name:
<Window x:Class="Web_Media_Seeker_WPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:Web_Media_Seeker_WPF"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Name="myWindow"
Title="MainWindow" Height="350" Width="525">
And then declare your binding as follows:
<Button x:Name="ribbutLayoutWindows" Command="{Binding comInitialiseWindows, ElementName=myWindow}" />

Categories