How to execute a Command from a different project in WPF? - c#

I am a newbie to WPF and am trying to create a simple test app, while maintaining an MVVM architecture.
So I split the application into two distinct projects: Tomato.UI (which contains the xaml files) and Tomato.ViewModels (which contains the classes to manage the interaction with the view).
For now I only have two files for one window: MainWindow.xaml and MainWindowViewModel.cs
In addition I added a reference to Tomato.ViewModels from Tomato.UI
So this is the MainWindow.xaml, where I've created a reference to the ViewModels namespace and where a Button is bound to a function on the MainWindowViewModel.cs file:
<Window x:Class="Tomato.UI.MainWindow"
xmlns:ViewModels="clr-namespace:Tomato.ViewModels"
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"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button
Content="START"
Click="ViewModels:MainWindowViewModel.ButtonStartClick"
HorizontalAlignment="Center" Margin="344,304,343.6,70" VerticalAlignment="Center" Width="106" Height="46">
</Button>
</Grid>
</Window>
In the related ViewModel class I have the following code
using System;
using System.Windows;
using System.Windows.Input;
using Tomato.Utils;
namespace Tomato.ViewModels
{
public class MainWindowViewModel
{
public ICommand ButtonStartClick { get; set; }
public MainWindowViewModel()
{
ButtonStartClick = new RelayCommand(new Action<object>(ButtonStart));
}
private void ButtonStart(object sender)
{
MessageBox.Show("Starting now!");
}
}
}
But when I try to execute the code I get this error that I can't solve:
Severity Code Description Project File Line Suppression State
Error CS1061 'MainWindow' does not contain a definition for 'ButtonStartClick' and no accessible extension method 'ButtonStartClick' accepting a first argument of type 'MainWindow' could be found (are you missing a using directive or an assembly reference?) Tomato.UI C:\Projects_personal\TomatoTimer\TomatoTimer\MainWindow.xaml 26 Active
A heartfelt thanks to anyone who is able to direct me on the right path :)

You only have to make a binding Command="{Binding ButtonStartClick}"
Click is an eventhandler, not command.
<Button Content="START"
Command="{Binding ButtonStartClick}"
HorizontalAlignment="Center" Margin="344,304,343.6,70"
VerticalAlignment="Center" Width="106" Height="46">
</Button>
Check you have setted correctly the DataContext for the Window (not for the button, the button inherits the datacontext of the parent if not setted).

I am a newbie to WPF and am trying to create a simple test app, while maintaining an MVVM architecture
Unfortunately, so far you're doing it wrong. :)
With the syntax you've tried to use the XAML, the property would have to be static, accessible through the type name itself. But the way you've declared your view model class, you need to reference a property path on binding markup, for a data context object that has been instantiated.
Note that currently, your XAML also doesn't declare a view model object. That can be done in a variety of ways, but the most straightforward would be to set the Windows.DataContext property.
Putting that all together, you get something like this:
<Window x:Class="Tomato.UI.MainWindow"
xmlns:ViewModels="clr-namespace:Tomato.ViewModels"
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"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<ViewModels:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Button
Content="START"
Command="{Binding ButtonStartClick}"
HorizontalAlignment="Center" Margin="344,304,343.6,70" VerticalAlignment="Center" Width="106" Height="46">
</Button>
</Grid>
</Window>

The View needs to bind to the view model instance in the DataContext, which, in this case, can all be done in XAML
<Window x:Class="Tomato.UI.MainWindow"
xmlns:ViewModels="clr-namespace:Tomato.ViewModels;assembly=Tomato.ViewModels"
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"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<ViewModels:MainWindowViewModel /> <!-- Set DataContext to View Model-->
</Window.DataContext>
<Grid>
<Button
Content="START"
Command="{Binding ButtonStartClick}" <!-- Bind to command -->
HorizontalAlignment="Center" Margin="344,304,343.6,70"
VerticalAlignment="Center" Width="106" Height="46" />
</Grid>
</Window>

Related

ActivateItemAsync in Caliburn.Micro

I recently started studying about MVVM, in which I'm now looking at the Caliburn.Micro framework. Unfortunately I could only see very old content and the framework documentation is outdated. I'm using Caliburn 4.0.173, which no longer has the ActivateItem method that was replaced by ActivateItemAsync, follow the code below: ShellViewModel.cs.
ShellViewModel.cs
public async void LoadPageOne()
{
await ActivateItemAsync(new FirstChildViewModel(), CancellationToken.None);
}
public async void LoadPageTwo()
{
await ActivateItemAsync(new SecondChildViewModel(), CancellationToken.None);
}
ShellView.Xaml
<!-- Row 5 -->
<Button x:Name="LoadPageOne" Grid.Row="5" Grid.Column="1"> Load First Page</Button>
<Button x:Name="LoadPageTwo" Grid.Row="5" Grid.Column="2"> Load Second Page</Button>
<!-- Row 6 -->
<ContentControl Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="5" x:Name="ActiveItem"/>
In the video he is using dotnet framework 4.6 and caliburn in 3.2, while I am using dotnet 6. Even adding everything I tried to find, even on github, the usercontrol screen does not change. Could someone tell me where I'm letting it go? I'm a junior programmer and I wanted to understand about this problem, instead of having to change everything to a previous version.
I tried this and it works.
If you want to use built in IoC and want to add messaging between view models you can check out my sample project.
ViewModels:
using Caliburn.Micro;
using System.Threading.Tasks;
namespace CaliburnMicroDemo.ViewModels;
public class ShellViewModel : Conductor<IScreen>
{
public async Task LoadPageOneAsync()
{
await ActivateItemAsync(new FirstChildViewModel());
}
public async Task LoadPageTwoAsync()
{
await ActivateItemAsync(new SecondChildViewModel());
}
}
public class FirstChildViewModel : Screen
{
}
public class SecondChildViewModel : Screen
{
}
ShellView.xaml:
<Window x:Class="CaliburnMicroDemo.Views.ShellView"
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"
mc:Ignorable="d"
Title="Shell Window" Height="450" Width="800">
<DockPanel>
<Button x:Name="LoadPageOneAsync" Content="Load First Page" DockPanel.Dock="Top"/>
<Button x:Name="LoadPageTwoAsync" Content="Load Second Page" DockPanel.Dock="Top"/>
<ContentControl x:Name="ActiveItem" DockPanel.Dock="Bottom"/>
</DockPanel>
</Window>
FirstChildView.xaml:
<UserControl x:Class="CaliburnMicroDemo.Views.FirstChildView"
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="450" d:DesignWidth="800">
<Grid>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="1"/>
</Grid>
</UserControl>
SecondChildView.xaml:
<UserControl x:Class="CaliburnMicroDemo.Views.SecondChildView"
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="450" d:DesignWidth="800">
<Grid>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" Text="2"/>
</Grid>
</UserControl>

Setting a datacontext via resources

Evidently using "Resources" to set an control's DataContext does not do what I think. I'm trying to stick close to MVVM. The following is an experiment in setting DataContext.
The MainWindow has a TabControl with two tabs, each displaying my pet's name, initally "Sam". Clicking the "ChangeName" button on Tab 1 changes the pet's name (to "Daisy") as expected. It does not change on Tab 2.
The content of Tab 2 is a Page, with its own DataContext, SecondTabViewModel. So I need to adjust the DataContext in the TextBlock in order to get at MyPet's name. This compiles ok, and Intellisense brings up the right things, so somehow within the control is being set. But the pet's name does not change.
Does the "StaticResource" generate instantiate a new copy of MainWindow or something? Can someone help me out? I'd love to know why this doesn't work, and what would work. This strategy for setting local DataContext is supposed to work according to the docs at https://learn.microsoft.com/en-us/dotnet/desktop/wpf/data/?view=netdesktop-5.0 but I must be misreading.
To abbreviate I've omitted some of the code (the pet class. But everything seems to be ok there, in I'm able to change the name on the first tab The Pet class implements INotifyPropertyChanged, I'm using the right handler etc.)
MainWindow.xmal
<Window x:Class="WpfApp9.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:WpfApp9"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<TabControl>
<TabItem Header="First Tab" Height="50">
<StackPanel>
<TextBlock Text="{Binding MyPet.Name}"/>
<Button Content="Change Name"
Command="{Binding ChangePetNameCommand}"/>
</StackPanel>
</TabItem>
<TabItem Header="Second Tab" Height="50">
<Frame Source="SecondTab.xaml"/>
</TabItem>
</TabControl>
</Grid>
</Window>
MainWindowViewModel
public class MainWindowViewModel
{
public Pet MyPet { get; set; }
public ICommand ChangePetNameCommand { get; set; }
public MainWindowViewModel()
{
MyPet = new Pet();
ChangePetNameCommand =
new RelayCommand(ChangePetName, (Object o) => true);
}
public void ChangePetName(object o)
{
MyPet.Name = "Daisy";
}
}
SecondTab.xmal
<Page x:Class="WpfApp9.SecondTab"
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"
xmlns:local="clr-namespace:WpfApp9"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="SecondTab">
<Page.DataContext>
<local:SecondTabViewModel/>
</Page.DataContext>
<Page.Resources>
<local:MainWindowViewModel x:Key="M"/>
</Page.Resources>
<Grid>
<StackPanel>
<TextBlock Text="{Binding Source={StaticResource M},
Path = MyPet.Name}"/>
</StackPanel>
</Grid>
</Page>
SecondTabviewModel
namespace WpfApp9
{
public class SecondTabViewModel
{
public SecondTabViewModel()
{
}
}
}
The lines
<Page.Resources>
<local:MainWindowViewModel x:Key="M"/>
</Page.Resources>
in SecondTab.xaml are creating a second MainWindowViewModel instance.
In other words, SecondTab does not operate on the original MainWindowViewModel.
You would somehow have to pass a reference to the original MainWindowViewModel instance to SecondTabViewModel.
Instead of using a Frame and a Page, SecondTab could perhaps be a UserControl that simply inherits the DataContext from its parent element, and you could pass a view model object like
<TabItem Header="Second Tab" Height="50">
<local:SecondTab DataContext="{Binding SecondTabVM}"/>
</TabItem>
where SecondTabVM is a property of MainWindowViewModel that holds a SecondTabViewModel instance.

Custom xmlns namespaces not working

I am trying to follow these instructions for separating tab content into separate files. Here is my file structure
I am trying to load file 2's content with file 1 so that they work together. Here is file 2:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<UserControl x:Key="Tab1Control">
<DataTemplate DataType="TabItem">
<TextBlock Text="Test text"></TextBlock>
</DataTemplate>
</UserControl>
</ResourceDictionary>
And the relevant portion of file 1:
<Window x:Class="MnMCharacterCreator.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:tabs="TabContent/TabAttributesContent">
...
<!--Creates a tabbing system, with a grid defined by each ItemsControl-->
<TabControl Name="WindowTabs">
<TabItem Name="WindowTab1" Header="Attributes">
<!--This is where the UserControl from file 2 should be loaded-->
After visiting these two related questions (with tens of others) and asking on C# chat, I'm left thinking that this is unusual:
Intellisense shows nothing for <tabs: and even if I manually type an existing name or something, an error message is given, meaning that it is not a designer issue. Here is the full solution in VS2012.
To be specific, the question I am trying to ask is how can I use content from another xaml file? If xmlns isn't possible, what is?
You may want to create a user control for tab item as ,
<UserControl x:Class="WPFSample.TabItem1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<Grid>
<TextBlock Height="100" Width="100" Text="Hi from tab item 1"/>
</Grid>
Then for using this add the namespace in the MainWindow as :
<Window x:Class="WPFSample.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"
xmlns:local="clr-namespace:WPFSample">
<Grid>
<TabControl>
<TabItem Header="XYZ">
<local:TabItem1/>
</TabItem>
</TabControl>
</Grid>
You are ready to use .
Hope you have changed the usercontrol xaml.cs from XYZ : Window to XYZ : UserControl and build the solution once .

DelegateCommand inside Listview with UserControl

Is there any method to use my DelegateCommand inside my ListView with UserControl:
UserControl:
<UserControl
x:Class="App13.UserControls.ItemTemplateControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App13"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=True}"
mc:Ignorable="d">
<Grid>
<Button Content="Click" Command="{Binding OpenCommand"/>
</Grid>
</UserControl>
There is no error in my MainViewModel. There is error in Binding.
I can easily use OpenCommand in MainPage xaml using this code:
d:DataContext="{d:DesignInstance Type=local:MainViewModel, IsDesignTimeCreatable=True}"
<Button Content="Click" Command="{Binding OpenCommand"/>
How can I bind OpenCommnad to my UserControl?
Sorry for my English and thanks in advance!
This is my ListView:
<ListView x:Name="peopleListBox">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<UserControls:ItemTemplateControl/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
In the construction of your UserControl do
this.DataContext = new MainViewModel();
d:DataContext is just the Design time DataContext setting which is not applied at runtime.
By the name of it, DesignInstance is meant for design-time and not run-time.
In MVVM there are two approches of setting your ViewModel.
ViewFirst or ViewModelFirst - depending wether you build your app top down or bottom up.
for ViewFirst You can set your DataContext from your xaml :
<UserControl
x:Class="App13.UserControls.ItemTemplateControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App13"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<UserControl.DataContext>
<local:MainViewModel/>
</UserControl.DataContext>
<!-- Rest of your implementation ... -->
</UserControl>
For ViewModelFirst, set it in your code behind (usually done from View's constructor)
this.DataContext = new MainViewModel();
If you want to bind a property from your viewModel to an Item in your ListBox, bind your button inside the UserControl as follows:
<Button Content="Click"
Command="{Binding DataContext.OpenCommand,
RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
/>
Hope this helps

How can I add a nested WPF window with a class reference within a root window?

I would like to do something such as the following, but I am getting an error that, "The attribute 'Class' from the XAML namespace is only accepted on the root element." Well, I guess my question is how can I abstract a Window class (in this case, the issue is with SomeOtherWindowClass below) out (since I have this class added to my project as a WPF window), and include it as a sub-window in my project?
<Window x:Class="myLogViewer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="myLogViewer"
Height="600"
Width="800"
Background="Black"
BorderBrush="Black"
Style="{DynamicResource MainWindow}"
WindowStyle="None">
<Grid Background="Black">
<Window x:Name="myOtherLogViewerWindow"
x:Class="myLogViewer.SomeOtherWindowClass"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Background="Black"
BorderBrush="Black"
Style="{DynamicResource myOtherLogViewerWindow}"
WindowStyle="None"/>
</Grid>
</Window>

Categories