WPF Communication between User Controls - c#

I'm trying to find the best way to communicate between two User Controls. I have a main XAML window which contains two User Controls which in turn contain various controls. The Code behind of each User Control simply sets the DataContext to a View Model that is associated to it. The View Model contains objects that are bound to the controls.
What I'd like to do is capture when a list box in User Control 1 changes selection, the new selected item be displayed in an edit box in User Control 2. As I'm using View Models I can't declare Dependency Properties so I was wondering what is the accepted way to perform this?
I've attached some basic code to show how I'm setting the controls.
Main Window XAML
<Window x:Class="CommsTest.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CommsTest.View"
Title="MainWindow" Height="350" Width="525">
<Grid>
<local:UserControl1 />
<local:UserControl2 />
</Grid>
UserControl1 XAML
<UserControl x:Class="CommsTest.View.UserControl1"
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="300" d:DesignWidth="300">
<Grid>
<ComboBox Height="23" HorizontalAlignment="Left" Margin="50,110,0,0" Name="comboBox1" VerticalAlignment="Top" Width="199" ItemsSource="{Binding Combo1}" />
</Grid>
UserControl1ViewModel.cs
class UserControl1ViewModel
{
private ObservableCollection<string> combo1 = new ObservableCollection<string>();
public ObservableCollection<string> Combo1
{
get { return combo1; }
}
}
UserControl2.XAML
<UserControl x:Class="CommsTest.View.UserControl2"
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="300" d:DesignWidth="300">
<Grid>
<TextBox Height="23" HorizontalAlignment="Left" Margin="63,84,0,0" Name="textBox1" VerticalAlignment="Top" Width="170" Text="{Binding Path=Text1}" />
</Grid>
UserControl2ViewModel.cs
class UserControl2ViewModel
{
private string text1;
public string Text1
{
get { return text1; }
set { text1 = value; }
}
}
How do I get UserControl2.Text1 to be the selected value of UserControl2.Combo1?
Thanks

While I understand that you are asking how to communicate between UserControls, I would suggest that the answer is to communicate between the view models. This can be easily achieved using delegate objects. In general, you'd need to have a parent view model that is common to the two child view models.
I recently answered a similar question, so I won't duplicate answers. Instead, I would ask you to take a look at the answer from the Passing parameters between viewmodels post here on StackOverflow which explains the solution with code examples.
UPDATE >>>
When I said that you need a common parent to your child view models, I don't mean anything to do with inheritance. I just mean that the parent holds a variable instance of each of the child view models... the parent instantiates the child view models.
Instead of creating the view model instance in the view code behind, you can do it in the parent view model and connect the view models to the views like this:
In Resources:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:UsersViewModel}">
<Views:UsersView />
</DataTemplate>
Then you just need to display an instance of the view model and the appropriate view will be displayed:
In ParentView:
<ContentControl Content="{Binding ViewModel}" />
In ParentViewModel:
public BaseViewModel ViewModel { get; set; } // Implement INotifyPropertyChanged
Then when you want to display a new view:
ViewModel = new UsersViewModel();
If your child views do not have a BaseViewModel and/or are not interchangable, then you could just add a property for each of them:
public MainViewmodel MainViewModel { get; set; } // Implement INotifyPropertyChanged
public UsersViewmodel UsersViewModel { get; set; } // properly for these properties
Either way, you'll need access to these view models from the parent view if you are going to be able to 'connect them together' with handlers.

I would suggest you, to have only one ViewModel and bind the DataContext to MainWindow.xaml, instead of doing it to each UserControl.
You should also implement INotifyPropertyChanged in your ViewModel to notify the UI whenever you change the value from the code or ViewModel.

Maybe you should think about your self-imposed restriction of not having dependency properties in user controls. MVVM is nice for the overal architecture, but you can overdo it if you put it into every class and control you plan to do.
If your user controls are just controls for the user, they should behave as such. I have never had to communicate with a TextBoxViewModel or ButtonViewModel, those are controls I simply use. Maybe yours is simple, too and does not need it's own viewmodel. Then you could communicate by using dependency properties as all other controls do.

Related

WPF C# Binding Data from another Class

So I'm missing something simple or losing my mind. I am trying to reuse a class for multiple pages in a WPF application and bind the properties to the pages that instance it. I've tried setting the DataContext but I'm missing something. I'm loading the StockAnalysis page and then creating instance of the PriceChart class (this is the class for reuse) and I want the properties set in the PriceChart class to be the data to bind to the Stock.xaml.cs page. Even in setting the DataContext it is still looking for the StockAnalysis object. Why?
Stock.xaml.cs
public partial class StockAnalysis : Page
{
PriceChart PChart = new PriceChart();
public StockAnalysis()
{
InitializeComponent();
//Load The Data
List<Stock> HistoricalPrice = Database.GetPrices(ticker);
//Create The Charts
this.DataContext = PChart;
PChart.ShowPriceChart(HistoricalPrice);
}
}
Stock.xaml (Look at the Last TexBlock for the Binding of "LastPrice")
<Page x:Class="Stock.StockAnalysis"
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:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
xmlns:local="clr-namespace:Stock"
mc:Ignorable="d"
d:DesignHeight="1000" d:DesignWidth="1200"
Title="Stock Analysis">
<StackPanel x:Name="LastClosePanel" Grid.Row="0" Grid.RowSpan="2" Grid.Column="5" Height="60" VerticalAlignment="Top" Margin="1,0,0,1" Style="{StaticResource LastCloseBackground}">
<TextBlock x:Name="LastCloseText" Foreground="OrangeRed" FontSize="12" HorizontalAlignment="Center" Margin="0,10,0,8">Last Close</TextBlock>
<TextBlock x:Name="LastCloseBind" Foreground="White" FontSize="16" HorizontalAlignment="Center" Text="{Binding LastPrice}"></TextBlock>
</StackPanel>
</Page>
PriceChart.cs (This is where I assign "LastPrice" in hopes to bind it to the TextBlock in stock.xaml.cs)
public class PriceChart
{
public string LastPrice { get; set; }
public void ShowPriceChart(List<Stock> FullList)
{
LastPrice = FullList[0].LastPrice.ToString("C");
//DO OTHER THINGS
}
}
The problem is that PriceChart doesn't implement any change notification. With the current code, this is how things will go when StockAnalysis gets created:
InitializeComponent() will create the TextBlocks and the binding. At this point, DataContext is null, so the binding will fail and the TextBlock stay empty.
this.DataContext = PChart will trigger a binding update (because DataContext is a DependencyProperty, which means it does support change notification). When the binding updates, it will pull the value of LastPrice, which is currently still empty.
ShowPriceChart will set the value of LastPrice, but because PriceChart doesn't support change notification, the binding doesn't know it needs to update, so the TextBlock stays empty.
To solve this, I would recomend your PriceChart implement the INotifyPropertyChanged interface per this article: How to: Implement Property Change Notification.
(Technically, moving PChart.ShowPriceChart(HistoricalPrice) before this.DataContext = PChart would also "solve" the problem, but only if you never need to update the bindings again after initialization.)

WPF autoscale changing control [duplicate]

I have a WPF application with multiple views. I want to switch from view 1 to view 2 and from there I can switch to multiple views. So I want a button on view 1 that loads view2 in the same window.
I tried those things, but can't get it to work.
How to navigate through windows with MVVM Light for WPF?
https://galasoft.ch/posts/2011/01/navigation-in-a-wp7-application-with-mvvm-light
From the first link, the problem is that I don't understand the ViewModelLocator code. They call the CreateMain(); function but where is this defined, and how can I switch to another view from inside a view.
Firstly, you don't need any of those toolkits/frameworks to implement MVVM. It can be as simple as this... let's assume that we have a MainViewModel, and PersonViewModel and a CompanyViewModel, each with their own related view and each extending an abstract base class BaseViewModel.
In BaseViewModel, we can add common properties and/or ICommand instances and implement the INotifyPropertyChanged interface. As they all extend the BaseViewModel class, we can have this property in the MainViewModel class that can be set to any of our view models:
public BaseViewModel ViewModel { get; set; }
Of course, you'd be implementing the INotifyPropertyChanged interface correctly on your properties unlike this quick example. Now in App.xaml, we declare some simple DataTemplates to connect the views with the view models:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use one of our BaseViewModel instances in our application, these DataTemplates will tell the framework to display the related view instead. We can display them like this:
<ContentControl Content="{Binding ViewModel}" />
So all we need to do now to switch to a new view is to set the ViewModel property from the MainViewModel class:
ViewModel = new PersonViewModel();
Finally, how do we change the views from other views? Well there are several possible ways to do this, but the easiest way is to add a Binding from the child view directly to an ICommand in the MainViewModel. I use a custom version of the RelayComand, but you can use any type you like and I'm guessing that you'll get the picture:
public ICommand DisplayPersonView
{
get { return new ActionCommand(action => ViewModel = new PersonViewModel(),
canExecute => !IsViewModelOfType<Person>()); }
}
In the child view XAML:
<Button Command="{Binding DataContext.DisplayPersonView, RelativeSource=
{RelativeSource AncestorType={x:Type MainView}}, Mode=OneWay}" />
That's it! Enjoy.
When i first started wiht MVVM I also struggled with the different MVVM-frameworks and especially the navigation part. Therefore I use this little tutorial i found, that Rachel Lim has created. It's very nice and well explained.
Have a look at it on the following link:
http://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Hope it helped you :)
Maybe this link will help you. Just set the NavigateTo property to the view which you need to display on the window.
As an example you can do something like
<Window x:Class="MainWindowView" 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:meffed="http:\\www.codeplex.com\MEFedMVVM"
meffed:ViewModelLocator.NonSharedViewModel="YourViewModel"
WindowStartupLocation="CenterScreen">
<Button meffed:NavigationExtensions.NavigateTo="firstview"
meffed:NavigationExtensions.NavigationHost="{Binding ElementName=_viewContainer}"
meffed:NavigationExtensions.NavigateOnceLoaded="False"
Visibility="Visible" />
<ContentControl x:Name="_viewContainer" Margin="0,0,0,10" />
<Window>
Then the class file would be
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
}
public ContentControl ViewContainer { get { return _viewContainer; } }
}
Then you can define each view as UserControl and then using the link I gave above bind the button's meffed:NavigationExtensions.NavigateTo="secondView". To target the ContentControl of the Window just use a RelativeSource binding. For e.g
meffed:NavigationExtensions.NavigationHost="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Path=ViewContainer}"
In each of the view just see that you annotate the code behind class definition with the [NavigationView("firstview")] and so on.
It is complicated for first time but it will be very easy once you understand the idea.
<ContentControl x:Name="K.I.S.S" Content="{Binding ViewModel, Converter={StaticResource ViewLocator}}"/>

Put a View Control in its ViewModel

I'm developing a Windows 8.1 Store app with C# and .Net Framework 4.5.1.
I'm trying to bind Password.SecurePassword to a ViewModel, and reading this SO answer I found a way to do it: Put the PasswordBox in my ViewModel.
But I don't know how to do it. I know how to bind Dependency Properties, but I don't know how to put that control on my ViewModel. This is my XAML:
<Page
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"
DataContext="{Binding MainViewModel, Source={StaticResource Locator}}"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<PasswordBox x:Name="userPassword" />
</Grid>
</Page>
What do I have to do?
You have several options but I'll just give you the basic option without third party libraries.
In your Page constructor. You can do something like this.
public Page()
{
var mainViewModel = this.DataContext as MainViewModel;
if(mainViewModel != null)
{
mainViewModel.PasswordBox = userPassword;
}
}
You can also set it on the Loaded event of the View and set the PasswordBox to the ViewModel.

Setting the datacontext of all sub-views in parent view

I have a XAML file of the form
CTC.XAML
<UserControl x:Class="KPI.CTC.UI.CTC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:uc="clr-namespace:KPI.CTC.UI"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<TabControl Grid.Row="1">
<TabItem Header="Industry">
<uc:Industry DataContext="{}"/>
</TabItem>
<TabItem Header="Templates">
<uc:Templates DataContext="{}"/>
</TabItem>
<TabItem Header="Item Calcs">
<uc:ItemCalcs DataContext="{}"/>
</TabItem>
</TabControl>
</UserControl>
where Industry.xaml,Templates.xaml and ItemCalcs.xaml have respective ViewModels in a separate namespace KPI.CTC.ViewModel. I wished to know is it possible to set the DataContext of all the 3 views in CTC.xaml only. I do not want to perform the tedious task of setting the DataContext separately for individual views.
Thanks in advance!!
If you look at the FrameworkElement.DataContext Property page on MSDN, you should see a section named Dependency Property Information:
This tells us that the DataContext DependencyProperty has a FrameworkPropertyMetadata value of Inherits, which means that the DataContext property will automatically be inherited by child controls. Therefore, if you had wanted to set the DataContext of all of the child controls to the same value as the parent, then this would happen automatically, but this is the only situation where this would happen.
So unless your situation is as above, you're going to have to write some code somewhere... the only question is where. Perhaps the simplest method is for you to use DataTemplates, although this will only work if you are data binding an instance of a different type of object for each user control. Try adding these DataTemplates into your Application.Resources:
<DataTemplate DataType="{x:Type ViewModels:IndustryViewModel}">
<Views:Industry />
</DataTemplate>
...
<DataTemplate DataType="{x:Type ViewModels:ItemCalcsViewModel}">
<Views:ItemCalcs />
</DataTemplate>
Using this method will free you from ever having to manually set a DataContext property. Just add a new DataTemplate for each view/view model pair that you have and then you can display your views/UserControls like this:
<ContentControl Content="{Binding ViewModelIntanceProperty}" />
There are plenty of ways to do it and it depends on your application architecture, complexity, etc. Some involve using so-called IoC containers.
The three simplest would be:
Declare VMs directly in XAML:
<TabItem Header="Industry">
<uc:Industry>
<uc:Industry.DataContext>
<vm:IndustryViewModel />
</uc:Industry.DataContext>
</uc:Industry>
</TabItem>
etc.
Declare VMs as 'main' ViewModel properties:
public IndustryViewModel IndustryViewModel
{
get;
set;
}
etc.
If TabControl ViewModels inherit from one base class, you may delcare a Dictionary
in 'main' VM containing the 3 TabControl ViewModels:
Dictionary<string, ViewModelBase> ViewModels
{
get;
set;
}
ViewModels = new Dictionary<string, ViewModelBase>();
ViewModels.Add("Industry", new IndustryViewModel());
// and so on...
and assign it in XAML:
<TabItem Header="Industry">
<uc:Industry DataContext="{Binding ViewModels[Industry]}"/>
</TabItem>

WPF MVVM navigate views

I have a WPF application with multiple views. I want to switch from view 1 to view 2 and from there I can switch to multiple views. So I want a button on view 1 that loads view2 in the same window.
I tried those things, but can't get it to work.
How to navigate through windows with MVVM Light for WPF?
https://galasoft.ch/posts/2011/01/navigation-in-a-wp7-application-with-mvvm-light
From the first link, the problem is that I don't understand the ViewModelLocator code. They call the CreateMain(); function but where is this defined, and how can I switch to another view from inside a view.
Firstly, you don't need any of those toolkits/frameworks to implement MVVM. It can be as simple as this... let's assume that we have a MainViewModel, and PersonViewModel and a CompanyViewModel, each with their own related view and each extending an abstract base class BaseViewModel.
In BaseViewModel, we can add common properties and/or ICommand instances and implement the INotifyPropertyChanged interface. As they all extend the BaseViewModel class, we can have this property in the MainViewModel class that can be set to any of our view models:
public BaseViewModel ViewModel { get; set; }
Of course, you'd be implementing the INotifyPropertyChanged interface correctly on your properties unlike this quick example. Now in App.xaml, we declare some simple DataTemplates to connect the views with the view models:
<DataTemplate DataType="{x:Type ViewModels:MainViewModel}">
<Views:MainView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:PersonViewModel}">
<Views:PersonView />
</DataTemplate>
<DataTemplate DataType="{x:Type ViewModels:CompanyViewModel}">
<Views:CompanyView />
</DataTemplate>
Now, wherever we use one of our BaseViewModel instances in our application, these DataTemplates will tell the framework to display the related view instead. We can display them like this:
<ContentControl Content="{Binding ViewModel}" />
So all we need to do now to switch to a new view is to set the ViewModel property from the MainViewModel class:
ViewModel = new PersonViewModel();
Finally, how do we change the views from other views? Well there are several possible ways to do this, but the easiest way is to add a Binding from the child view directly to an ICommand in the MainViewModel. I use a custom version of the RelayComand, but you can use any type you like and I'm guessing that you'll get the picture:
public ICommand DisplayPersonView
{
get { return new ActionCommand(action => ViewModel = new PersonViewModel(),
canExecute => !IsViewModelOfType<Person>()); }
}
In the child view XAML:
<Button Command="{Binding DataContext.DisplayPersonView, RelativeSource=
{RelativeSource AncestorType={x:Type MainView}}, Mode=OneWay}" />
That's it! Enjoy.
When i first started wiht MVVM I also struggled with the different MVVM-frameworks and especially the navigation part. Therefore I use this little tutorial i found, that Rachel Lim has created. It's very nice and well explained.
Have a look at it on the following link:
http://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/
Hope it helped you :)
Maybe this link will help you. Just set the NavigateTo property to the view which you need to display on the window.
As an example you can do something like
<Window x:Class="MainWindowView" 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:meffed="http:\\www.codeplex.com\MEFedMVVM"
meffed:ViewModelLocator.NonSharedViewModel="YourViewModel"
WindowStartupLocation="CenterScreen">
<Button meffed:NavigationExtensions.NavigateTo="firstview"
meffed:NavigationExtensions.NavigationHost="{Binding ElementName=_viewContainer}"
meffed:NavigationExtensions.NavigateOnceLoaded="False"
Visibility="Visible" />
<ContentControl x:Name="_viewContainer" Margin="0,0,0,10" />
<Window>
Then the class file would be
public partial class MainWindowView : Window
{
public MainWindowView()
{
InitializeComponent();
}
public ContentControl ViewContainer { get { return _viewContainer; } }
}
Then you can define each view as UserControl and then using the link I gave above bind the button's meffed:NavigationExtensions.NavigateTo="secondView". To target the ContentControl of the Window just use a RelativeSource binding. For e.g
meffed:NavigationExtensions.NavigationHost="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}},Path=ViewContainer}"
In each of the view just see that you annotate the code behind class definition with the [NavigationView("firstview")] and so on.
It is complicated for first time but it will be very easy once you understand the idea.
<ContentControl x:Name="K.I.S.S" Content="{Binding ViewModel, Converter={StaticResource ViewLocator}}"/>

Categories