I've got many user controls like this:
PageManageCustomers.xaml.cs:
public partial class PageManageCustomers : BasePage
{
...
}
which inherit from:
PageBase.cs:
public class BasePage : UserControl, INotifyPropertyChanged
{
...
}
Since PageBase.cs has no accompanying XAML file, I have to put the XAML that it refers to in each of the user controls which inherit it, e.g. the following block is repeated in every XAML file of every control that inherits PageBase:
<DataTemplate x:Key="manageAreaCellTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{DynamicResource ManageLinkStyle}"
Tag="{Binding Id}" Text="Delete" MouseDown="System_Delete_Click"/>
<TextBlock Text=" "/>
<TextBlock Style="{DynamicResource ManageLinkStyle}"
Tag="{Binding Id}" Text="Edit" MouseDown="System_Edit_Click"/>
</StackPanel>
</DataTemplate>
I'm trying to put this block into a resource file but can't get the syntax right, it says:
'ResourceDictionary' root element
requires a x:Class attribute to
support event handlers in the XAML
file. Either remove the event handler
for the MouseDown event, or add a
x:Class attribute to the root element.
Or perhaps I could read these blocks in with XamlReader` somehow?
How can I put this repeated block of code in one place so that it is not repeated in every XAML file that inherits BagePage?
Here is a reproducable example of this problem:
Window1.xaml:
<Window x:Class="TestXamlPage8283.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="MainContent"/>
</Window>
Window1.xaml.cs:
using System.Windows;
namespace TestXamlPage8283
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
Page1 page1 = new Page1();
MainContent.Children.Add(page1);
Page2 page2 = new Page2();
MainContent.Children.Add(page2);
}
}
}
Page1.xaml:
<local:BasePage x:Class="TestXamlPage8283.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestXamlPage8283"
Height="40" Width="300">
<StackPanel>
<TextBlock Text="{Binding PageTitle}"
FontSize="14"
FontWeight="Bold"/>
<TextBlock Text="This is XAML that is specific to page one." />
</StackPanel>
</local:BasePage>
Page1.xaml.cs:
namespace TestXamlPage8283
{
public partial class Page1 : BasePage
{
public Page1()
{
InitializeComponent();
PageTitle = "Page One";
}
}
}
Page2.xaml:
<local:BasePage x:Class="TestXamlPage8283.Page2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestXamlPage8283"
Height="40" Width="300">
<StackPanel>
<TextBlock Text="{Binding PageTitle}"
FontSize="14"
FontWeight="Bold"/>
<TextBlock Text="This is XAML that is specific to page two." />
</StackPanel>
</local:BasePage>
Page2.xaml.cs:
namespace TestXamlPage8283
{
public partial class Page2 : BasePage
{
public Page2()
{
InitializeComponent();
PageTitle = "Page Two";
}
}
}
BasePage.cs:
using System.Windows.Controls;
using System.ComponentModel;
namespace TestXamlPage8283
{
public class BasePage : UserControl, INotifyPropertyChanged
{
#region ViewModelProperty: PageTitle
private string _pageTitle;
public string PageTitle
{
get
{
return _pageTitle;
}
set
{
_pageTitle = value;
OnPropertyChanged("PageTitle");
}
}
#endregion
public BasePage()
{
DataContext = this;
}
#region INotifiedProperty Block
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
How do I take this block
<TextBlock Text="{Binding PageTitle}"
FontSize="14"
FontWeight="Bold"/>
out of Page1.xaml and Page2.xaml and put it in one place so I can refer to it from Page1.xaml and Page2.xaml? (so that when I want to change FontSize=14 to FontSize=16, I just have to change it in one place)
Use resource dictionaries - add a MyDictionary.xaml file to your project, setting its Build Action to "Page":
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<DataTemplate x:Key="manageAreaCellTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock Style="{DynamicResource ManageLinkStyle}"
Tag="{Binding Id}" Text="Delete" MouseDown="System_Delete_Click"/>
<TextBlock Text=" "/>
<TextBlock Style="{DynamicResource ManageLinkStyle}"
Tag="{Binding Id}" Text="Edit" MouseDown="System_Edit_Click"/>
</StackPanel>
</DataTemplate>
</ResourceDictionary>
Then in other XAML files you refer to it with:
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="MyDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
... some other local resources ...
</ResourceDictionary>
and refer to your resource as Template={StaticResource manageAreaCellTemplate}.
Related
i'm making in a music player program, and now I need to add a page to show the playlist's detail, such as the musics and the music's artists in the playlist.I'm going to make some musics' card and add them to a stackpanel.
I have achieved this by getting this panel object and calling its add function, but can I achieve it through data binding?
This is the way i used before
<StackPanel x:Name="PanelMusics" />
PanelMusics.Children.Add(...);
You should try using ListView or ItemsRepeater instead of StackPanel.
This is a simple example how to use ListView with the help of the CommunityToolkit.Mvvm NuGet package.
MusicItem.cs
namespace MusicCardsSample;
public class MusicItem
{
public string Title { get; set; } = string.Empty;
public string Artist { get; set; } = string.Empty;
}
MainPageViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using System.Collections.ObjectModel;
namespace MusicCardsSample;
// The "CommunityTookit.Mvvm" needs this class to be "partial".
public partial class MainPageViewModel : ObservableObject
{
// The "CommunityTookit.Mvvm" creates
// an UI-interactable property "MusicItems" for you.
[ObservableProperty]
private ObservableCollection<MusicItem> musicItems = new();
// The "CommunityTookit.Mvvm" creates
// an "AddMusicItemCommand" command for you.
[RelayCommand]
private void AddMusicItem(MusicItem item)
{
musicItems.Add(item);
}
}
MainPage.xaml.cs
using Microsoft.UI.Xaml.Controls;
namespace MusicCardsSample;
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
}
private MainPageViewModel ViewModel { get; } = new();
}
MainPage.xaml
<Page
x:Class="MusicCardsSample.MainPage"
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:local="using:MusicCardsSample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid RowDefinitions="Auto,*">
<StackPanel
Grid.Row="0"
Orientation="Horizontal">
<TextBox
x:Name="TitleTextBlock"
PlaceholderText="Title" />
<TextBox
x:Name="ArtistTextBlock"
PlaceholderText="Artist" />
<Button
Command="{x:Bind ViewModel.AddMusicItemCommand}"
Content="Add">
<Button.CommandParameter>
<local:MusicItem
Title="{x:Bind TitleTextBlock.Text, Mode=OneWay}"
Artist="{x:Bind ArtistTextBlock.Text, Mode=OneWay}" />
</Button.CommandParameter>
</Button>
</StackPanel>
<ListView
Grid.Row="1"
ItemsSource="{x:Bind ViewModel.MusicItems, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:MusicItem">
<!--You can design your music cards here.-->
<StackPanel Orientation="Vertical">
<TextBlock
FontSize="9"
Text="{x:Bind Artist}" />
<TextBlock Text="{x:Bind Title}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
I'm trying to create a sort of ShellView without using thirdparty frameworks, and I'm doing trying to do it via. ContentControls and UserControls.
I can navigate/switch the UserControls that is showed inside the ContentControls as long as the commands are fired from outside the UserControls, but nothing is happening when I move the code inside a button in a UserControl.
Currently I'm having one MainWindow.XAML, where I have one ContentControl.
This ContentControl is hosting either a LoginWindowUserControl, or a UserWindowUserControl - I want to be able switch the UserControl from within one of the UserControls.
My MainWindow.XAML looks like this:
<Window x:Class="ModelHealthApplication.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:vms="clr-namespace:ModelHealthApplication.ViewModels"
xmlns:ia="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:views="clr-namespace:ModelHealthApplication.Views.UserControls"
xmlns:local="clr-namespace:ModelHealthApplication"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type vms:CurrentWindowUserStateViewModel}">
<views:UserView />
</DataTemplate>
<DataTemplate DataType="{x:Type vms:LoginWindowViewModel}">
<views:LoginView />
</DataTemplate>
<vms:NavigationViewModel x:Key="nVm" />
</Window.Resources>
<ia:Interaction.Triggers>
<ia:EventTrigger EventName="Loaded">
<ia:InvokeCommandAction Command="{Binding Source={StaticResource nVm}, Path=OpenLoginWindowCommand}" />
</ia:EventTrigger>
</ia:Interaction.Triggers>
<Grid DataContext="{StaticResource nVm}">
<DockPanel>
<Button Content="Test" DockPanel.Dock="Left" Command="{Binding OpenUserWindowStateCommand}" />
<ContentControl x:Name="WindowUserState" Content="{Binding CurrentWindowUserState}" />
</DockPanel>
</Grid>
And my UserWindowUserControl looks like this:
<UserControl x:Class="ModelHealthApplication.Views.UserControls.UserView"
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:vms="clr-namespace:ModelHealthApplication.ViewModels"
xmlns:views="clr-namespace:ModelHealthApplication.Views.UserControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ModelHealthApplication.Views.UserControls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<DataTemplate DataType="{x:Type vms:MyModelsViewModel}">
<views:MyModelsView/>
</DataTemplate>
<DataTemplate DataType="{x:Type vms:MyAccountViewModel}">
<views:MyAccountView />
</DataTemplate>
<vms:NavigationViewModel x:Key="nVm" />
</UserControl.Resources>
<Grid>
<DockPanel DataContext="{StaticResource nVm}">
<Grid DockPanel.Dock="Top" Background="{StaticResource MainBlue}" Height="25">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Grid.Column="0">
<Button Content="My Models"
HorizontalAlignment="Center"
Command="{Binding OpenMyModelsCommand}"
Style="{StaticResource NavButtonStyle}"/>
<Button Content="My Account"
HorizontalAlignment="Center"
Command="{Binding OpenMyAccountCommand}"
Style="{StaticResource NavButtonStyle}"
/>
</StackPanel>
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Right" Grid.Column="1" Margin="0, 0, 10, 0">
<TextBlock VerticalAlignment="Center" Foreground="White">
<Run Text="Logged in as:" FontWeight="Bold"/>
<Run Text="{Binding LoggedInAs}" d:Text="TestUser" />
</TextBlock>
<TextBlock Margin="20, 0 ,0 ,0" Text="Log Out" VerticalAlignment="Center" TextDecorations="Underline" Foreground="{StaticResource ComplenetarySecondOrange}" Background="{DynamicResource MainBlue}" Cursor="Hand">
<TextBlock.InputBindings>
<MouseBinding Command="{Binding OpenLoginWindowCommand}" MouseAction="LeftClick" />
</TextBlock.InputBindings>
</TextBlock>
</StackPanel>
</Grid>
<ContentControl x:Name="Pages" Content="{Binding SelectedViewModel}" />
</DockPanel>
</Grid>
</UserControl>
When I press the "Log Out" button/textblock I want to navigate back to the login window, but I can only do this from buttons that exist outside the UserControl.
I've read several other post similar to this, but I haven't found a solution that fits my needs - I tried using RelativeSource but without success - maybe someone can see what I'm doing wrong.
I'm using a "NavigationViewModel" to hold the commands which as I mentioned, works fine outside the UserControls:
using ModelHealthApplication.Commands;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
namespace ModelHealthApplication.ViewModels
{
public class NavigationViewModel : INotifyPropertyChanged
{
public ICommand OpenMyModelsCommand { get; set; }
public ICommand OpenMyAccountCommand { get; set; }
public ICommand OpenUserWindowStateCommand { get; set; }
public ICommand OpenLoginWindowCommand { get; set; }
private object currentWindowUserState;
public object CurrentWindowUserState
{
get { return currentWindowUserState; }
set
{
currentWindowUserState = value;
OnPropertyChanged("CurrentWindowUserState");
}
}
private object selectedViewModel;
public object SelectedViewModel
{
get { return selectedViewModel; }
set
{
selectedViewModel = value;
OnPropertyChanged("SelectedViewModel");
}
}
public NavigationViewModel()
{
OpenMyModelsCommand = new OpenMyModelsCommand(this);
OpenMyAccountCommand = new OpenAccountCommand(this);
OpenUserWindowStateCommand = new OpenUserWindowStateCommand(this);
OpenLoginWindowCommand = new OpenLoginWindowCommand(this);
}
public void OpenUserWindowState(object obj)
{
CurrentWindowUserState = new CurrentWindowUserStateViewModel();
}
public void OpenLoginWindow(object obj)
{
CurrentWindowUserState = new LoginWindowViewModel();
}
public void OpenMyModels(object obj)
{
SelectedViewModel = new MyModelsViewModel();
}
public void OpenMyAccount(object obj)
{
SelectedViewModel = new MyAccountViewModel();
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
}
Ended up solving this with a great tutorial from SingletonSean on Youtube.
Posting here if anyone comes across this, and has the same issue.
Singleton Sean MVVM Navigatoin
I have a window called RolledPaper.xaml with a textblock called SequenceValue.
SequenceValue is defined in another window called CounterSettings.xaml by typing in a textbox called SequenceRequested. I would like to have SequenceValue be always in sinch with SequenceRequested.
I tryed and failed using the same datacontext for both windows.
here it is the code for RolledPaper.xaml:
<Window x:Class="Numbering3.View.RolledPaper"
WindowStyle="None"
ResizeMode="NoResize"
BorderBrush="LightGray"
BorderThickness="5"
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:Numbering3.View"
xmlns:vm="clr-namespace:Numbering3.ViewModel"
xmlns:cv="clr-namespace:Numbering3.Helper"
xmlns:vo="clr-namespace:Numbering3.ViewModel"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="RolledPaper" Height="750" Width="1218"
Background="{DynamicResource WindowBrush}"
DataContextChanged="Rolledpaper_DataContextChanged">
<Window.DataContext>
<vm:ViewModelRolledPaper/>
</Window.DataContext>
<TextBlock x:Name="SequenceValue" Grid.Column="3" HorizontalAlignment="Left" Height="19" Margin="72,310,0,0" TextWrapping="Wrap"
Background="White" VerticalAlignment="Top" Width="98" Text="{Binding _SequenceValueToShow.SequenceValuetoShow, Mode=TwoWay, FallbackValue=NNN, TargetNullValue=NNN, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
code behind:
public partial class RolledPaper : Window
{
public RolledPaper()
{
WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;
SaveKeeper.fromMain = false;
InitializeComponent();
this.MouseLeftButtonDown += delegate { this.DragMove(); };
DataContextChanged += new DependencyPropertyChangedEventHandler(Rolledpaper_DataContextChanged);
}
the CounterSetting window:
<Window x:Class="Numbering3.View.CounterSettings"
...
...
<Window.DataContext>
<ViewModelCounterSettings/>
</Window.DataContext>
<Grid Margin="0,-19,-0.4,0">
<TextBox x:Name="SequenceRequested" PreviewTextInput="SequenceValidationTextBox" MaxLength="10" HorizontalAlignment="Left" Height="23" Margin="154,324,0,0" TextWrapping="Wrap"
Text="{Binding Path=_SequenceValueToShow.SequenceValueToKeep, FallbackValue='NNN', TargetNullValue ='NNN', Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }" VerticalAlignment="Top" Width="120" TextChanged="SequenceRequested_TextChanged" />
</Grid>
its code behind:
public partial class CounterSettings : Window
{
public CounterSettings()
{
InitializeComponent();
this.MouseLeftButtonDown += delegate { this.DragMove(); };
DataContextChanged += new DependencyPropertyChangedEventHandler(CounterSettings_DataContextChanged);
}
And the SequeneValue class:
public class SequenceValue : INotifyPropertyChanged
{
public string SequenceValueToKeep
{
get
{
return _sequenceValueToKeep=_sequenceProcessor.GetSequence();
}
set
{
if (_sequenceValueToKeep != value)
{
__sequenceValueToKeep = value;
RaisePropertyChanged("SequenceValueToKeep");
}
}
}
private void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null)
{ PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I need to put a value in the textbox of countersetting =>string Sequencevalue.SequenceValueToKeep is updated => textblock in RolledPaper window shows Sequencevalue.SequenceValueToKeep .
Thank you.
Solutio 1:
You can use one Static ViewModel to get the to to know each other.
in App.xaml
<Application.Resources>
...
<MainViewModel x:Key="mainViewModel" />
</Application.Resources>
and then in CounterSetting.xaml
<Window otherProperties=...
DataContext="{Binding ViewModelCounterSettings, Source={StaticResource mainViewModel}}"/>
and in RolledPaper.xaml
<Window otherProperties=...
DataContext="{Binding ViewModelRolledPaper, Source={StaticResource mainViewModel}}"/>
The MainViewModel does inits your two ViewModel and makes them accessible as properties and also gives the the same instance of SequenceValue
Solution 2:
Create an object of SequenceValue as an static resource, like
<Application.Resources>
...
<SequenceValue x:Key="sequenceValue"/>
</Application.Resources>
and set it as the DataContext of your TextBox and TextBlock
<TextBox DataContext="{StaticResource sequenceValue}"
Text="{Binding SequenceValueToKeep, FallbackValue='NNN', TargetNullValue='NNN', Mode=TwoWay, UpdateSourceTrigger=PropertyChanged }"
... />
<TextBlock DataContext="{StaticResource sequenceValue}"
Text="{Binding SequenceValuetoShow, FallbackValue=NNN, TargetNullValue=NNN, UpdateSourceTrigger=PropertyChanged}"
I am a newbie in WPF, I have a problem concern binding two different ViewModels to two UserControls
that will be attached to two Tabpages in a Tabcontrol.
My code snippets are as follows:
MainWindow.xaml
<Window.Resources>
<local:UserControl1Model x:Key="Control1Model" />
<local:UserControl2Model x:Key="Control2Model" />
</Window.Resources>
<Grid HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="592">
<Grid HorizontalAlignment="Left" Height="45" Margin="0,330,-1,-45" VerticalAlignment="Top" Width="593">
<Button Content="Button" HorizontalAlignment="Left" Margin="490,5,0,0" VerticalAlignment="Top" Width="75" Click="Button_Click"/>
</Grid>
<TabControl HorizontalAlignment="Left" Height="330" VerticalAlignment="Top" Width="592" >
<TabItem x:Name="UserControl1TabItem" Header="User Control 1" >
<Grid x:Name="UserControl1Tabpage" Background="#FFE5E5E5" Margin="0,0,-4,-2" Height="300" VerticalAlignment="Top" IsEnabled="true" >
<local:UserControl1 VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Control1Model}}" />
</Grid>
</TabItem>
<TabItem x:Name="UserControl2TabItem" Header="User Control 2">
<Grid x:Name="UserControl2Tabpage" Background="#FFE5E5E5">
<local:UserControl2 VerticalAlignment="Top" DataContext="{Binding Source={StaticResource Control2Model}}" />
</Grid>
</TabItem>
</TabControl>
</Grid>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
private UserControl1Model _userControl1Model = new UserControl1Model();
private UserControl2Model _userControl2Model = new UserControl2Model();
public MainWindow()
{
InitializeComponent();
_userControl1Model.Message = "Hello";
_userControl2Model.Message = "Test";
}
private void Button_Click(object sender, RoutedEventArgs e)
{
// Will do something
}
}
UserControl1Model.cs
public class UserControl1Model : INotifyPropertyChanged
{
private string _message;
public string Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public UserControl1Model()
{
}
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string message)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(message));
}
}
#region INotifyPropertyChanged Members
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
For trying purpose, the content of UserControl2Model.cs is as same as UserControl1Model.cs
UserControl1.xaml
<UserControl.Resources>
<app:UserControl1Model x:Key="Control1Model" />
</UserControl.Resources>
<Grid Margin="0,0,0,42" DataContext="{Binding Source={StaticResource Control1Model}}">
<Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>
<TextBox x:Name="Conrol1ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap"
Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="466" />
</Grid>
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
}
UserControl2.xaml
<UserControl.Resources>
<app:UserControl2Model x:Key="Control2Model" />
</UserControl.Resources>
<Grid Margin="0,0,0,42" DataContext="{Binding Source={StaticResource Control2Model}}">
<Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>
<TextBox x:Name="Conrol2ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap"
Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="466" />
</Grid>
For trying purpose, the content of UserControl2.xaml.cs is as same as UserControl1.xaml.cs
My problem is the initial values, "Hello" and "Test" for the two user controls, which are initialized in MainWindow.xaml.cs cannot
be "binded" into the user controls textboxes. What am I doing wrong or missing?
When you declare resources like this
<Window.Resources>
<local:UserControl1Model x:Key="Control1Model" />
<local:UserControl2Model x:Key="Control2Model" />
</Window.Resources>
You are actually constructing new instances of UserControl1Model and UserControl2Model instead using the ones you declared in MainWindow.cs
Also you are not creating any ViewModel for the MainWindow. You should create a MainWindowViewModel like such
public class MainWindowViewModel : INotifyPropertyChanged
{
public ViewModelLocator
{
this.FirstModel= new UserControl1Model
{
Message = "Hello";
}
this.SecondModel = new UserControl2Model
{
Message = "Test";
}
}
private UserControl1Model firstModel
public UserControl1Model FirstModel
{
get
{
return this.firstModel;
}
set
{
this.firstModel= value;
OnPropertyChanged("FirstModel");
}
}
// Same for the UserControl2Model
// implementation of the INotifyPropertyChanged
}
Also you would need to set the DataContext for the MainWindow.
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainWindowViewModel();
}
And remove the resources from the UserControl xamls. You are already defining the DataContext in the MainWindow.xaml but the binding should be bound from the MainWindowViewModel as such.
<local:UserControl1 VerticalAlignment="Top" DataContext="{Binding FirstModel}" />
Create a ViewModelLocator. you can find various sites on the internet for this subject.
a simple one would be:
public class ViewModelLocator
{
public UserControl1Model UserControl1Model { get; set; } = new UserControl1Model();
public UserControl2Model UserControl2Model { get; set; } = new UserControl2Model();
public ViewModelLocator
{
UserControl1Model.Message = "Hello";
UserControl2Model.Message = "Test";
}
}
then you can use it in your views
<UserControl.Resources>
<app:ViewModelLocator x:Key="ViewModelLocator" />
</UserControl.Resources>
<Grid Margin="0,0,0,42" DataContext="{Binding UserControl2Model Source={StaticResource ViewModelLocator}}">
<Label Content="Test:" HorizontalAlignment="Left" Margin="48,57,0,0" VerticalAlignment="Top" Width="47"/>
<TextBox x:Name="Conrol2ModelTextbox" HorizontalAlignment="Left" Height="23" Margin="90,59,0,0" TextWrapping="Wrap"
Text="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Top" Width="466" />
</Grid>
Suppose, I create this instance in the xaml as follows: <Window.Resources> <local:MainWindowViewModel x:Key="MainWindowViewModel" /> </Window.Resources> instead of creating inside MainWindow.xaml.cs. Then, how can I reference this instance inside MainWindow.xaml.cs, e.g. to get value from MainWindowViewModel.ViewModelLocator.FirstModel.Message ?
Like this:
MainWindowViewModel viewModel = this.Resources["MainWindowViewModel"] as MainWindowViewModel;
//access any properties of viewModel here...
I use .NETFramework,Version=v4.6.1
I have a Window, MainWindow. This is the XAML:
<Window x:Class="VexLibrary.DesktopClient.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VexLibrary.DesktopClient.Views"
Title="MainWindow" Height="600" Width="800">
<Grid>
<StackPanel>
<Grid Style="{StaticResource TitleBar}">
<Border Style="{StaticResource TitleBarBorder}">
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
<Label Style="{StaticResource TitleBarTime}">12:05 AM</Label>
<StackPanel Orientation="Horizontal">
<Label Style="{StaticResource TitleBarUsername}">Hassan</Label>
<Button>
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>
</Grid>
<Frame Width="700" Height="507" Source="Pages/Dashboard.xaml" />
</StackPanel>
</Grid>
</Window>
Note the:
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
The DataContext is set as follows in the MainWindow.xaml.cs constructor:
this.DataContext = new MainViewModel();
In the <Frame>, a Page Dashboard.xamlis loaded.
The page Dashboard.xaml has the source:
<Page x:Class="VexLibrary.DesktopClient.Views.Pages.Dashboard"
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:VexLibrary.DesktopClient.Views.Pages"
mc:Ignorable="d"
d:DesignHeight="460" d:DesignWidth="690"
Title="Page1">
<Grid Width="690" Height="460" HorizontalAlignment="Center" VerticalAlignment="Center">
<!-- Members, Users, Books -->
<!-- Returns, Subscriptions, Statistics -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Button Style="{StaticResource MenuButton}" Grid.Column="0" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="0" Grid.Row="1"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="1" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="1" Grid.Row="1"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="0"></Button>
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="1" Command="{Binding ViewStatistics}"></Button>
</Grid>
</Page>
In the Dashboard.xaml.cs constructor, I have defined the DataContext like this: DataContext = new DashboardViewModel();
The DashboardViewModel.cs source code is like this (omitted namespaces)
namespace VexLibrary.DesktopClient.ViewModels
{
class DashboardViewModel : ViewModel
{
private MainViewModel parentViewModel;
public DashboardViewModel()
{
this.parentViewModel = new MainViewModel();
}
public ICommand ViewStatistics
{
get
{
return new ActionCommand(p => this.parentViewModel.LoadPage("Statistics"));
}
}
}
}
Now, in this code, notice the Button with the Command:
<Button Style="{StaticResource MenuButton}" Grid.Column="2" Grid.Row="1" Command="{Binding ViewStatistics}"></Button>
It successfully calls the Command and the parent LoadPage method is executed correctly. The parent viewmodel looks like this:
namespace VexLibrary.DesktopClient.ViewModels
{
public class MainViewModel : ViewModel
{
private string currentPageTitle;
public string CurrentPageTitle
{
get
{
return this.currentPageTitle;
}
set
{
currentPageTitle = value;
NotifyPropertyChanged();
}
}
public void LoadPage(string pageName)
{
this.CurrentPageTitle = pageName;
Console.WriteLine(CurrentPageTitle);
}
}
}
The CurrentPageTitle is successfully updated. However, it is not updated in the view.
The parent view model inherits ViewModel which basically has this code:
namespace VexLibrary.Windows
{
public abstract class ViewModel : ObservableObject, IDataErrorInfo
{
public string this[string columnName]
{
get
{
return OnValidate(columnName);
}
}
[Obsolete]
public string Error
{
get
{
throw new NotImplementedException();
}
}
protected virtual string OnValidate(string propertyName)
{
var context = new ValidationContext(this)
{
MemberName = propertyName
};
var results = new Collection<ValidationResult>();
bool isValid = Validator.TryValidateObject(this, context, results, true);
if (!isValid)
{
ValidationResult result = results.SingleOrDefault(p =>
p.MemberNames.Any(memberName =>
memberName == propertyName));
return result == null ? null : result.ErrorMessage;
}
return null;
}
}
}
ObservableObject.cs:
namespace VexLibrary.Windows
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
// [CallerMemberName] automatically resolves the property name for us.
protected void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChangedEventHandler handler = PropertyChanged;
Console.WriteLine(handler == null);
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
After debugging, I found out, the NotifyPropertyChanged is invoked, but the handler is always null. How do I fix this? This is not updating the text in the MainWindow.xaml. I tested to see if the property value is changed, and yes, it is changed in the MainViewModel.cs
Also, I tested whether the label itself is visible or not. For that, I gave the variable a value and it correctly displays, but it is not updated.
The DashboardViewModel is instantiating a new instance of the MainViewModel rather than using the instance assigned to the DataContext of the MainWindow (and therefore the instance the view is bound to).
For your code to work you need to pass the correct instance of the MainViewModel to the DashboardViewModel as it is this instance that will have a handler for the property changed event.
EDIT: As per the comment below, you should instantiate your sub viewmodels as follows:
namespace VexLibrary.DesktopClient.ViewModels
{
public class MainViewModel : ViewModel
{
private ViewModel _currentViewModel;
public MainViewModel()
{
_currentViewModel = new DashboardViewModel(this);
}
public ViewModel CurrentViewModel
{
get { return _currentViewModel; }
private set
{
_currentViewModel = value;
OnPropertyChanged();
}
}
}
}
You can then amend your Xaml such that the frame gets it's data context from the CurrentViewModel property as follows:
<Window x:Class="VexLibrary.DesktopClient.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VexLibrary.DesktopClient.Views"
Title="MainWindow" Height="600" Width="800">
<Grid>
<StackPanel>
<Grid Style="{StaticResource TitleBar}">
<Border Style="{StaticResource TitleBarBorder}">
<DockPanel>
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal">
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
<Label Style="{StaticResource TitleBarTitle}" Content="{Binding Path=CurrentPageTitle, UpdateSourceTrigger=PropertyChanged}" ></Label>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right">
<Label Style="{StaticResource TitleBarTime}">12:05 AM</Label>
<StackPanel Orientation="Horizontal">
<Label Style="{StaticResource TitleBarUsername}">Hassan</Label>
<Button>
<TextBlock Style="{StaticResource TitleBarIcon}" Text="" />
</Button>
</StackPanel>
</StackPanel>
</DockPanel>
</Border>
</Grid>
<Frame Width="700" Height="507" Source="Pages/Dashboard.xaml" DataContext="{Binding CurrentViewModel}"/>
</StackPanel>
</Grid>
</Window>
And will then need to use some form of view location / navigation to change the frame to display the correct view. Some MVVM frameworks (for example, CaliburnMicro) can do this for you.
Again, in order to make this code testable, the instantiation of sub-viewmodels should be delegated to a factory class which is injected into the MainViewModel.
Hope it helps.