Multiple binding ViewModel to View MVVM - wpf

Please help me understand. I have a View PanoramaPage.xaml with two PanoramaItem. First item is a list of a news from a some web service, second item is a list of users the service. News and Users are differnt Models.
View:
<controls:PanoramaItem Header="users">
<ListBox Margin="0,0,-12,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,0,17">
<StackPanel Width="311">
<TextBlock Text="{Binding Name}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding Aboutself}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
</controls:Panorama>
<controls:PanoramaItem Header="news">
<ListBox Margin="0,0,-12,0">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17" Width="432" Height="78">
<TextBlock Text="{Binding Title}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding Content}" TextWrapping="Wrap" Margin="12,-6,12,0" Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</controls:PanoramaItem>
Under MVVM I should have two ViewModel's for two controls News ListBox and Users ListBox or one ViewModel for one xaml PanoramaPage.xaml.
PanoramaPageViewModel
public class PanoramaPageViewModel : INotifyPropertyChanged
{
private ObservableCollection<User> userDataSource;
private ObservableCollection<News> newsDataSource;
public ObservableCollection<User> UserDataSource
{
get
{
if (this.userDataSource == null)
{
this.userDataSource = new ObservableCollection<User>();
}
return this.userDataSource;
}
}
public ObservableCollection<News> NewsDataSource
{
get
{
if (this.newsDataSource == null)
{
this.newsDataSource = new ObservableCollection<News>();
}
return this.newsDataSource;
}
}
// LoadUsers(), LoadNews(), etc
}
OR
UsersViewModel
public class UsersViewModel : INotifyPropertyChanged
{
private ObservableCollection<User> userDataSource;
public ObservableCollection<User> UserDataSource
{
get
{
if (this.userDataSource == null)
{
this.userDataSource = new ObservableCollection<User>();
}
return this.userDataSource;
}
}
//LoadUsers() etc
}
NewsViewModel
public class NewsViewModel : INotifyPropertyChanged
{
private ObservableCollection<News> newsDataSource;
public ObservableCollection<News> NewsDataSource
{
get
{
if (this.newsDataSource == null)
{
this.newsDataSource = new ObservableCollection<News>();
}
return this.newsDataSource;
}
}
//LoadNews() etc
}
What do you think?

Single ViewModel. Not each and every control of view has its own view model. You set the ViewModel as the DataContext of the whole view.
Even if you go with two viewmodels, you will need to have the parent viewmodel which will be containing the instances of these two viewmodels. This parent view model will serve as the Datacontext of whole view and the child controls will set their datacontext to these child viewmodels, so you will have to change your bindings also.
But single view single view model is what mvvm is.
Thanks

Related

How can I DataBind a textbox from the parent window with values from the child with MVVM?

I just took over a project from another programmer who is no longer here. It was created using the MVVM Pattern (using the MVVM Light toolkit). I am new to MVVM and have been trying to learn the basics fast. Currently I am having trouble getting a selected value from a Child Window back to the Parent Window.
From another post on SO I learned that I should use the same ViewModel for both the parent and the child so I think I have the basics right. However I have not been able to get the selected values back to the parent. Below is a sample set of code similar to the production code.
My ViewModel for both pages is here
public class MainViewModel : ViewModelBase
{
private Vendor selectedVendor = null;
List<Vendor> vendors;
public MainViewModel()
{
OpenVendorWindowCommand = new RelayCommand(VendorSelect);
VendorSelectedCommand = new RelayCommand(VendorSelected);
LoadVendors();
}
public ICommand OpenVendorWindowCommand { get; private set; }
public ICommand VendorSelectedCommand { get; private set; }
void VendorSelect()
{
Messenger.Default.Send(new NotificationMessage("SelectVendor"));
}
public Vendor SelectedVendor
{
get { return selectedVendor; }
set
{
if (selectedVendor != value)
{
selectedVendor = value;
RaisePropertyChanged();
}
}
}
void VendorSelected()
{
Console.WriteLine(SelectedVendor.VendorName);
}
public List<Vendor> Vendors
{
get
{
return vendors;
}
set
{
if (vendors != value)
{
vendors = value;
RaisePropertyChanged();
}
}
}
private void LoadVendors()
{
DataTable dt = new DataTable();
dt = Vendor.GetVendors();
Vendors = new List<Vendor>();
foreach (DataRow row in dt.Rows)
{
Vendors.Add(new Vendor()
{
VendorID = Convert.ToInt32(row["VendorID"]),
VendorCode = Convert.ToString(row["VendorCode"]),
VendorName = Convert.ToString(row["VendorName"])
});
}
}
}
I am at the point that the Child Window opens and I am able to select a vendor from a ListBox. After the selection I press a button (VendorSelectedCommand) and it is at that point I want the textbox on the Parent Window to be filled with the SelectedVendor.VendorName value.
This is the XAML from my Child Window
<StackPanel VerticalAlignment="Center">
<ListBox
Height="200"
Margin="5"
HorizontalAlignment="Stretch"
Background="GhostWhite"
ItemsSource="{Binding Vendors}"
SelectedItem="{Binding Path=SelectedVendor, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="Black" BorderThickness="3">
<StackPanel Margin="15">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="175" />
<ColumnDefinition Width="100" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
FontWeight="SemiBold"
Foreground="Black"
Text="{Binding VendorName}" />
<TextBlock
Grid.Column="1"
FontWeight="SemiBold"
Foreground="Black">
<Run Text=" (" />
<Run Text="{Binding VendorCode}" />
<Run Text=") " />
</TextBlock>
</Grid>
</StackPanel>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding VendorSelectedCommand}" Content="Send Vendor Back" />
</StackPanel>
And lastly this is the XAML for the Parent Window with what I think is the correct binding
<StackPanel VerticalAlignment="Center">
<TextBox Margin="10" Text="{Binding SelectedVendor.VendorName}" />
<Button
Margin="10"
Command="{Binding OpenVendorWindowCommand}"
Content="Select Vendor" />
</StackPanel>
I have tried every possible combination of Binding Syntax that I can think of and have tried multiple different ways in the code behind to catch and bind it but have not been able to get it right. What is missing from my ViewModel to make this work?
Edit For clarity (and in response to a comment) I am adding the DataContext, which I had in the Constructor of the Views.
public partial class VendorView : Window
{
private MainViewModel _vm = null;
public VendorView()
{
InitializeComponent();
_vm = new MainViewModel();
DataContext = _vm;
}
}
Edit #2 I am opening the second page with this. This is very simple sample app with only two pages so I didn't want to get bogged down with navigation until I have a better handle on Binding.
private void NotificationMessageReceived(NotificationMessage msg)
{
if (msg.Notification == "SelectVendor")
{
var vendorView = new VendorView();
vendorView.ShowDialog();
}
}

View is not binding correctly to ViewModel

I cannot get the View to bind correctly to the ViewModel. When it displays, it only shows the string version of the ViewModel.
I have seen: Setting Window.Content to ViewModel - simple data template not working. But the link is no longer available.
I'm trying to use https://msdn.microsoft.com/en-us/magazine/dd419663.aspx, as a template.
MainWindow.xaml
<Window x:Class="DemoApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:DemoApp.ViewModel"
xmlns:vw="clr-namespace:DemoApp.View">
<Window.Resources>
<DataTemplate DataType="{x:Type vm:TestViewModel}">
<vw:TestView/>
</DataTemplate>
<DataTemplate x:Key="ClosableTabItemTemplate">
<DockPanel Width="120">
<Button
Command="{Binding Path=CloseCommand}"
Content="X"
Cursor="Hand"
DockPanel.Dock="Right"
VerticalContentAlignment="Bottom"
Width="16" Height="16"/>
<ContentPresenter
Content="{Binding Path=DisplayName}"
VerticalAlignment="Center"/>
</DockPanel>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4" />
</DataTemplate>
</Window.Resources>
<DockPanel>
<Border
Grid.Column="2"
Style="{StaticResource MainBorderStyle}">
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
Header="Workspaces"
Style="{StaticResource MainHCCStyle}" />
</Border>
</DockPanel>
</Window>
MainWindowViewModel.cs
// ommitted for clarity. This is directing to the view model correctly. It's the binding between View and ViewModel that is not
TestView.xaml
public class TestViewModel : WorkspaceViewModel, INotifyPropertyChanged,
{
public Model.Test _test;
public string DisplayName {get; set;}
public class TestViewModel(Model.Test t)
{
DisplayName = "Test Display Name";
_model = t;
}
// INofifyPropertyChanged Members removed for clarity
}
Test.cs
public class Test
{
public string FirstName {get; set;}
public string LastName {get; set;}
public static DisplayTest()
{
return new Test();
}
}
Displays:
DemoApp.ViewModel.TestViewModel;
However, when I go to the MainWindow.xaml and actually type in into a DockPanel, it will display correctly...
Thank you!!
UPDATE:
MainWindowViewModel.cs Properties
public ReadOnlyCollection<CommandViewModel> Commands
{
get
{
if (_commands == null)
{
List<CommandViewModel> cmds = this.CreateCommands();
_commands = new ReadOnlyCollection<CommandViewModel>(cmds);
}
return _commands;
}
}
public ObservableCollection<WorkspaceViewModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceViewModel>();
_workspaces.CollectionChanged += this.OnWorkspacesChanged;
}
return _workspaces;
}
}
In the View there was a Data Context Declared. This was confusing the binding it looks like. Once the Data Context in the View was removed and the MainWindowResourses kept the data context, the view is displayed as it should.

How to put an Expander inside Datatemplate?

Strange one.
I have a contentcontrol on a WPF form, this loads a datatemplate within it.
This shows up fine (handwritten summary code so ignore errors/lack of attributes):
<DataTemplate>
<Label Content="Found datatemplate" />
</DataTemplate>
This however renders blank
<DataTemplate>
<Expander Header="Why dont I show">
<Label Content="Found datatemplate" />
</Expander>
</DataTemplate>
I have set the expander to visibile, isexpanded to true etc and no matter what it doesn't render at all.
Confused- is this just not possible?
I've recently done something similar to what you're describing and it worked for me. I have an ItemsControl that binds to a collection of view models, each of which contains a UserControl representing custom content. I implemented the ItemsControl.ItemTemplate to display the custom control inside an Expander like this:
<ItemsControl Margin="0,20,0,0" ItemsSource="{Binding ControlItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,0,0,0"
BorderBrush="#E7E7E7"
BorderThickness="0,1,0,0"
Padding="20,0">
<Expander Foreground="#E7E7E7"
IsExpanded="{Binding Path=IsExpanded,
Mode=TwoWay}">
<Expander.Header>
<Grid>
<TextBlock HorizontalAlignment="Left"
VerticalAlignment="Center"
FontSize="24"
Text="{Binding Title}" />
</Grid>
</Expander.Header>
<DockPanel>
<ScrollViewer MinHeight="250">
<ContentControl Content="{Binding Control}" />
</ScrollViewer>
</DockPanel>
</Expander>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is what my view model looks like:
public class SidePanelControlItem : ModelBase
{
private bool _isExpanded;
public SidePanelControlItem(UserControl control)
{
if (control == null) { throw new ArgumentNullException("control");}
Control = control;
}
public string Title { get; set; }
public UserControl Control { get; private set; }
public bool IsExpanded
{
get { return _isExpanded; }
set
{
_isExpanded = value;
OnPropertyChanged("IsExpanded");
}
}
}

ObservableCollection Images in Listbox to Content Control master detail WPf

I have an observablecollection of Images that get populated via the following code:
<StackPanel Orientation="Horizontal" Grid.Column="0">
<ListBox ItemsSource="{Binding BigImageView}" IsSynchronizedWithCurrentItem="True"
SelectedIndex="0" SelectedItem="{Binding CurrentItem}" />
</StackPanel>
<ContentControl Name="Detail" Content="{Binding BigImageView, Mode=OneWay}"
Margin="9,0,0,0" Grid.Column="2" HorizontalAlignment="Left" VerticalAlignment="Top"/>
However the Content Control is supposed to bind to the BigImageView via an ObservableCollection
BigImage = new ObservableCollection<Image>();
_listView = CollectionViewSource.GetDefaultView(BigImage);
_listView.CurrentChanged += new EventHandler(OnCurrentChanged);
public System.ComponentModel.ICollectionView BigImageView
{
get
{
return _listView;
}
set
{
_listView = value;
OnPropertyChanged("BigImageView");
}
}
I want to return the image to the content control when I move the listbox. I have been racking my brain and trying everyhitn but it does not work. any help would be appreciated.
There is no need to bind the selecteditem, the collectionview should take care of that.
Try this:
<ListBox ItemsSource="{Binding BigImageView}" IsSynchronizedWithCurrentItem="True" />
<ContentControl Name="Detail" Content="{Binding BigImageView, Mode=OneWay}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
<ContentControl.ContentTemplate>
1
Create a viewmodel with a list and a selected item:
public class BigImageViewModel : INotifyPropertyChanged
{
private string bigImage;
//string for path?
public ObservableCollection<string> BigImageView {get; set; } //Of course, make sure it has a value
public string SelectedBigImage
{
get { return bigImage; }
set { bigImage = values; NotifyPropertyChanged("SelectedBigImage"); }
}
}
Set this object on the DataContext of your control in the constructor:
DataContext = new BigImage(); //Make sure you initialize your list
Set the ListBox ItemsSource to your BigImage list, bind your SelectedItem to BigImageView
and use that in your content control:
<ListBox ItemsSource="{Binding BigImageView}" SelectedItem={Binding SelectedBigImage} />
ContentControl:
<ContentControl Name="Detail" Content="{Binding SelectedBigImage, Mode=OneWay}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/> <!-- Nice template for showing your string BigImage -->
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>
2
Or screw that view model:
Set the list directly in the constructor (after the InitializeComponent() ):
myListBox.ItemsSource = ObservableCollection<string>(); //Make sure you initialize your list with whatever your object is..
Give the list a name:
And bind with an ElementName binding to your selected item:
<ContentControl Name="Detail" Content="{Binding ElementName=myListBox, Path=SelectedItem}" VerticalAlignment="Top">
<ContentControl.ContentTemplate>
<DataTemplate>
<Image Source="{Binding}"/> <!-- Nice template for showing your string BigImage -->
</DataTemplate>
<ContentControl.ContentTemplate>
</ContentControl>

How to read all textbox values(Map to database fileds) and store them in Database in WPF - MVVM

I am new to WPF and MVVM. After a long invention i got to know how to retrieve data from database and bind it to a itemcontrol/listview/gridview.
But my problem i am not getting how to read bunch of textbox values and store as a new record in database.
Here is my sample code..
View
<ItemsControl ItemsSource="{Binding AllEmployees}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Text="{Binding FirstName}" Margin="4"/>
<TextBox Width="100" Text="{Binding LastName}" Margin="4"/>
<TextBox Width="100" Text="{Binding Age}" Margin="4"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- For new Employee Details -->
<StackPanel>
<TextBox x:Name="FirstName"/>
<TextBox x:Name="LastName"/>
<TextBox x:Name="Age"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
My cs file is
public ObservableCollection<DataAccess.Employee> AllEmployees
{
get;
private set;
}
public EmployeeListViewModel(EmployeeRepository employeeRepository)
{
if (employeeRepository == null)
{
throw new ArgumentNullException("employeeRepository");
}
_employeeRepository = employeeRepository;
this.AllEmployees = new ObservableCollection<DataAccess.Employee>
(_employeeRepository.ListAll());
}
Now how could i store a new employee Firstname, Lastname, Age in database by reading those text boxes..
How to write function for NewEmployeeCommand event to read the textboxes( mapping of textboxes to appropriate datafileds in database) and store the data in database.
Thanks a Lot !
if you're trying to use MVVM just need to:
Create your ViewModel to contain all the properties your View needs
Bind to those properties in Xaml
For example:
public class EmployeeListViewModel
{
public ObservableCollection<Employee> AllEmployees {get;private set;}
public string FirstName {get;set;}
public string LastName {get;set;}
public int? Age {get;set;}
public ICommand NewEmployeeCommand {get;set;}
//You need to connect to this method by using a Delegate/RelayCommand see link below
public void AddNewEmployee()
{
//Add your real code here to actually insert into the db
var result = InsertEmployeeIntoDatabase(FirstName,LastName,Age);
//You probably want to add this new employee to the list now ;)
AllEmployees.Add(result);
//Now you probably want to reset your fields
FirstName = null;
LastName = null;
Age = null;
}
}
Click here for an implementation of a delegate command
And then just edit your xaml like this:
<ItemsControl ItemsSource="{Binding AllEmployees}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Text="{Binding FirstName}" Margin="4"/>
<TextBox Width="100" Text="{Binding LastName}" Margin="4"/>
<TextBox Width="100" Text="{Binding Age}" Margin="4"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!-- For new Employee Details -->
<StackPanel>
<TextBox Text={Binding FirstName}"/>
<TextBox Text={Binding LastName}"/>
<TextBox Text={Binding Age}"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
You could pass references in the command parameter:
<StackPanel>
<TextBox x:Name="FirstName"/>
<TextBox x:Name="LastName"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}">
<Button.CommandParameter>
<x:Array Type="{x:Type TextBox}">
<x:Reference Name="FirstName"/>
<x:Reference Name="LastName"/>
</x:Array>
</Button.CommandParameter>
</Button>
</StackPanel>
Depending on what kind of event you use you case the parameter and get the values:
TextBox[] textBoxes = e.Parameter as TextBox[]; //RoutedEvent
TextBox[] textBoxes = parameter as TextBox[]; //If the executed handler provides the parameter
string firstName = textBoxes[0].Text;
string lastName = textBoxes[1].Text;
//create entry; store in DB
Via binding:
<Button.CommandParameter>
<local:MyEntry FirstName="{Binding ElementName=FirstName, Path=Text}"
LastName="{Binding ElementName=LastName, Path=Text}"/>
</Button.CommandParameter>
MyEntry entry = parameter as MyEntry;
//store in DB
you dont read the Textbox values. you need a NewEmployeeViewModel and bind the TextBoxes to the properties.
EDIT:
just create a class with INotifyPropertyChanged and the Properties you need.
public class NewEmployee : INotifyPropertyChanged
{
public string FirstName
{
get{return this._firstname;}
set{this._firstname = value;
OnPropertyChanged("FirstName");}
}
//... other properties
}
xaml
<StackPanel DataContext={Binding MyNewEmployeeProperty}>
<TextBox x:Name="FirstName" Text={Binding FirstName}/>
<TextBox x:Name="LastName" Text={Binding LastName}/>
<TextBox x:Name="Age" Text={Binding Age}/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
I got the Correct answer.
My New entry form in xmal Should be
<StackPanel>
<TextBox Text={Binding Employee.FirstName}"/>
<TextBox Text={Binding Employee.LastName}"/>
<TextBox Text={Binding Employee.Age}"/>
<Button Content="New" Command="{Binding NewEmployeeCommand}"/>
</StackPanel>
My cs file
public class EmployeeListViewModel : INotifyPropertyChanged
{
Employee _employee;
RelayCommand _addNewEmployee;
EmployeeRepository _employeeRepository;
public Employee Employee
{
get
{
return _employee;
}
set
{
_employee = value;
OnPropertyChanged("Employee");
}
}
public void NewEmployeeCommand()
{
if(_addNewEmployee == null)
{
_addNewEmployee = new RelayCommand( param => NewEmployeeCommandExecute(),
param => NewEmployeeCommandCanExecute
);
}
}
void NewEmployeeCommandExecute()
{
_employeeRepository.Add(Employee);
_employeeRepository.Save();
}
bool NewEmployeeCommandCanExecute
{
get
{
return true;
}
}
}

Resources