New to WPF here. The application being built has a list of users being pulled from a database for display in a "Users" Window, navigable from a "Main" Window. The list seems to be transferred to the code behind, but the list of users isn't displaying in the "Users" Window ListBox. Does anyone see why this isn't displaying? Many thanks in advance!
"Main" Window directing:
UsersViewModel Usersvm = new UsersViewModel();
Usersvm.Users = new List<UserViewModel>();
DbEntities db = new DbEntities();
var pulledUsers = db.uspGetUsers().ToList();
foreach (var result in pulledUsers)
{
var pulledUser = new UserViewModel
{
FirstName = result.FirstName,
LastName = result.LastName,
EMail = result.Email,
UserID = result.UserID,
Position = result.Position,
EndDate = result.EndDate,
};
Usersvm.Users.Add(pulledUser);
}
new UsersWindow(Usersvm).Show();
UsersWindow code behind:
public partial class UsersWindow : Window
{
public UsersWindow(UsersViewModel uvm)
{
InitializeComponent();
listboxUsers.ItemsSource = uvm.Users;
}
}
UsersWindow.xaml:
<Window x:Class="DbEntities.UsersWindow"
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:DbEntities"
mc:Ignorable="d"
Title="UsersWindow" Height="Auto" Width="900">
<Window.Resources>
<Style x:Key="borderBase" TargetType="Border">
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="1" />
</Style>
</Window.Resources>
<StackPanel>
<TextBlock x:Name="textBlock" Height="21" Margin="0,0,161,0" TextWrapping="Wrap"
Text="Users Page" VerticalAlignment="Top" RenderTransformOrigin="1.022,0.409" HorizontalAlignment="Right" Width="344"/>
<Grid>
<Grid Grid.IsSharedSizeScope="True">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="151*" />
<ColumnDefinition Width="95*" />
<ColumnDefinition Width="110*" />
<ColumnDefinition Width="351*" />
<ColumnDefinition Width="75*" />
<ColumnDefinition Width="110*" />
</Grid.ColumnDefinitions>
<Border Style="{StaticResource borderBase}">
<TextBlock HorizontalAlignment="Center" Text="Last Name" />
</Border>
<Border Grid.Column="1" Style="{StaticResource borderBase}">
<TextBlock HorizontalAlignment="Center" Text="First Name" />
</Border>
<Border Grid.Column="2" Style="{StaticResource borderBase}">
<TextBlock HorizontalAlignment="Center" Text="Position" />
</Border>
<Border Grid.Column="3" Style="{StaticResource borderBase}">
<TextBlock HorizontalAlignment="Center" Text="Email" />
</Border>
<Border Grid.Column="4" Style="{StaticResource borderBase}">
<TextBlock HorizontalAlignment="Center" Text="End Date" />
</Border>
<Border Grid.Column="5" Style="{StaticResource borderBase}">
<TextBlock HorizontalAlignment="Center" />
</Border>
<ListBox x:Name="listboxUsers" HorizontalAlignment="Center" Height="Auto" Margin="3,25,0,0" VerticalAlignment="Top" Width="889"
ItemsSource="{Binding Users}" Grid.ColumnSpan="6">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="LastNameColumn" />
</Grid.ColumnDefinitions>
<Border Style="{StaticResource borderBase}">
<TextBlock Text="{Binding LastName}"/>
</Border>
<Border Style="{StaticResource borderBase}">
<TextBlock Text="{Binding FirstName}"/>
</Border>
<Border Style="{StaticResource borderBase}">
<TextBlock Text="{Binding Position}"/>
</Border>
<Border Style="{StaticResource borderBase}">
<TextBlock Text="{Binding Email}"/>
</Border>
<Border Style="{StaticResource borderBase}">
<TextBlock Text="{Binding EndDate}"/>
</Border>
<Border Style="{StaticResource borderBase}">
<Button Content="Edit" x:Name="editButton" Click="editButton_Click"/>
</Border>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Grid>
</Grid>
</StackPanel>
</Window>
And finally, the UsersViewModel, with a list of the user contact information:
public partial class UsersViewModel : Window
{
public List<UserViewModel> Users { get; set; }
}
EDIT (Solved):
Ed Plunkett's comments and answer directly solved the original ListBox question, and using that input combined with ThyArtIsCode's, which was all neatly presented by Monty, the process is much more elegant. Thanks to all who replied - there's a ton of great learning material here.
I see a couple things wrong...
First, your ViewModel is inheriting Window. If there isn't a particular reason for this, get rid of it. If you want to notify UI of changes made to your collection (which should ideally be part of your view model), make the view model inherit INotifyPropertyChanged.
You are also binding to ListBox here:
ItemsSource="{Binding Users}"
AND setting the ItemsSource again here:
listboxUsers.ItemsSource = uvm.Users;
BAD! If you are binding in XAML, there's absolutely no need to set the ItemsSource again. Need to modify the collection? Do so with the collection directly.
Also, since you're new to WPF, I figured I'd add some suggestions that helped me when I first started learning:
If you want things to go quicker, add IsAsync=True to your ListBox binding. This will enable asynchronous binding (amazing, I know).
Virtualize the crap out of that ListBox (simply add following to ListBox):
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Recycling"
And one last thing, though others suggested using an ObservableCollection, it also comes with a performance hit when using large data. Even if you don't intend to have large data, it always safer to use a BindingList anyway. In fact, ObservableCollection has an upper hand when working with smaller data sets.
They are much quicker and share many similar properties as the OC.
You've got a few things to fix here, but nothing very complicated. Just a lot of MVVM/XAML housekeeping stuff.
The way MVVM works in XAML is that your viewmodels don't know about your views --- ideally they don't know about any UI at all. To make that happen with stuff like message boxes and file open dialogs can involve some contortions, but we're not going there right now. Incidentally, you definitely don't want to derive a view model from Window -- that's a UI class, and it doesn't do anything that view models need a base class to do.
So your viewmodels have public properties, which you've got, but when those properties change, they should fire notifications off into the darkness. To do that, you implement INotifyPropertyChanged on your viewmodel, and you fire the PropertyChanged event when a property changes. The UI will subscribe to those notifications -- if your view model is the DataContext of the element whose property is bound (clear as mud -- more on that later).
When a viewmodel exposes a collection, it usually uses ObservableCollection, because that class fires notifications on add/remove/etc. List doesn't do that. ObservableCollection comes with some overhead from all the notification stuff, so don't just use it everywhere -- still use List when all you need is a List.
So UsersViewModel.Users needs to be of type ObservableCollection<UserViewModel><UserViewModel>, and when the collection is replaced, fire PropertyChanged.
private ObservableCollection<UserViewModel> _users =
new ObservableCollection<UserViewModel>();
ObservableCollection<UserViewModel> Users {
get { return _users; }
set {
_users = value;
// Implementations of this are everywhere on Google, very simple.
OnPropertyChanged("Users");
// Or in C#6
//PropertyChanged?.Invoke(new PropertyChangedEventArgs(nameof(Users)));
}
}
And of course make sure UserViewModel also implements INotifyPropertyChnaged and fires notifications when its own property values change.
Next, your XAML binding for ItemsSource on the ListBox is correct, but assigning a collection to that property in code behind will break it. A {Binding ...} in XAML isn't just an assignment: It creates an instance of the Binding class, which sits in the middle and manages all the notification event business I mentioned above. You can create bindings programmatically, but doing it in XAML is much simpler and in 99.5+% of cases does everything you need.
Most importantly, the window needs to know about your viewmodel. Make that happen by assigning an instance of UsersViewModel to the window's DataContext. The window's child controls will inherit that DataContext, and all bindings will be evaluated in that context.
public partial class UsersWindow : Window
{
public UsersWindow(UsersViewModel uvm)
{
InitializeComponent();
var vm = new UsersViewModel();
// initialize vm if needed
DataContext = vm;
}
}
You could have the window's creator pass in a UsersViewModel instance via the window's constructor as well.
OK try this.....
ViewModel....
class Base_ViewModel : INotifyPropertyChanged
{
public RelayCommand<UserViewModel> editButton_Click_Command { get; set; }
public Base_ViewModel()
{
editButton_Click_Command = new RelayCommand<UserViewModel>(OneditButton_Click_Command);
this.Users = new ObservableCollection<UserViewModel>();
this.Users.Add(new UserViewModel() { FirstName = "John", LastName = "Doe", EMail = "JohnDoe#yahoo.com", EndDate = "02-01-2016", Position = "Developer", UserID = "AADD543" });
}
private ObservableCollection<UserViewModel> _Users;
public ObservableCollection<UserViewModel> Users
{
get { return _Users; }
set { _Users = value; NotifyPropertyChanged("Users"); }
}
private void OneditButton_Click_Command(UserViewModel obj)
{ // put a break-point here and you will see the data you want to Edit in obj
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
User Class.....
public class UserViewModel : INotifyPropertyChanged
{
private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set { _FirstName = value; NotifyPropertyChanged("FirstName"); }
}
private string _LastName;
public string LastName
{
get { return _LastName; }
set { _LastName = value; NotifyPropertyChanged("LastName"); }
}
private string _EMail ;
public string EMail
{
get { return _EMail; }
set { _EMail = value; NotifyPropertyChanged("EMail"); }
}
private string _UserID;
public string UserID
{
get { return _UserID; }
set { _UserID = value; NotifyPropertyChanged("UserID"); }
}
private string _Position;
public string Position
{
get { return _Position; }
set { _Position = value; NotifyPropertyChanged("Position"); }
}
private string _EndDate;
public string EndDate
{
get { return _EndDate; }
set { _EndDate = value; NotifyPropertyChanged("EndDate"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
XAML.....
Set the Window x:Name....
<Window x:Name="Base_V"......
DataContext
<Window.DataContext>
<ViewModels:Base_ViewModel/>
</Window.DataContext>
And the rest of the View....
<Grid>
<DataGrid Name="DataGrid1" ItemsSource="{Binding Users}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding DataContext.editButton_Click_Command, ElementName=Base_V}" CommandParameter="{Binding}">Edit</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
you should end up with something like this....
Update 1
In the constructor of the Base_ViewModel
this.Users.Add(new UserViewModel() { FirstName = "John", LastName = "Doe", EMail = "JohnDoe#yahoo.com", EndDate = "02-01-2016", Position = "Developer", UserID = "AADD543" });
this.Users.Add(new UserViewModel() { FirstName = "Fred", LastName = "Doe", EMail = "FredDoe#yahoo.com", EndDate = "02-01-2016", Position = "Developer", UserID = "AADD543" });
// empty record to allow the use to Add a new record
this.Users.Add(new UserViewModel());
When the user selects the Edit button for the empty record they are in effect simply filling in a blank record, Once they have filled that in, make sure to add another blank record to produce a new (empty row) in the DataGrid ....
Related
I have a problem with DataGrid and after week trying to solve this I am out of mind.
I am using Prism.MVVM to handle loading properties, INotifyPropertyChanged etc.
My datagrid is being populated from database (EF) by of course ViewModel. When I double click on the row edit window will open with populated fields etc. I am doing this by "SelectedItem". Everything to this moment is working fine, but:
• When I editing my "Stock" textbox I see in the ProductListView window that this value is changing in realtime and even if I hit Cancel (and the window closed) it stays as I left it in ProductView and even after opening edit window again "Stock" value remain wrong, but in database the value is correct.
• When I edit for example "Category" or "Name" I do not see changes in datagrid in realtime (in this case values in DataGrid stay correct), but if I hit Cancel (and the window closed) an reopen edit window again value remain wrong, but in database the value is correct.
I tried to DeepCopy it and then Override SelectedItem back after edit and it work for database (it getting updated), but view (DataGrid) does not.
ProductListView:
<DataGrid ColumnWidth="Auto" Grid.Row="1" ItemsSource="{Binding ProductsCollectionView, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem}" AutoGenerateColumns="False" IsReadOnly="True"
ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Auto">
Methods/Commands responsible for edit in ProductListViewModel:
private Product _selectedItem;
public Product SelectedItem
{
get => _selectedItem;
set => SetProperty(ref _selectedItem, value);
}
public ProductListViewModel() // Contructor
{
_service = new ProductService();
Load();
ProductsCollectionView = CollectionViewSource.GetDefaultView(Data);
ProductsCollectionView.Filter = FilterProducts;
LoadCommands();
EditCommand = new DelegateCommand(Edit);
}
public ICommand EditCommand { get; set; }
private void Edit()
{
var dialog = new ProductView(SelectedItem);
dialog.ShowDialog();
if (dialog.DialogResult == true)
{
_service.UpdateProductData(SelectedItem);
_service.SaveChanges();
}
}
ProductView (Btw. I am using same View for Adding and Editing products, that is why I passing "SeleectedItem through the constructor)
<StackPanel Margin="0,0,13,0" Grid.Column="0">
<TextBlock Text="Product type" Style="{StaticResource StackPanelTextBox}"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="41*" />
<ColumnDefinition Width="251*"/>
<ColumnDefinition Width="31*" />
</Grid.ColumnDefinitions>
<ComboBox IsEditable="True" IsTextSearchEnabled="True" ItemsSource="{Binding ProductTypes}" DisplayMemberPath="Name" SelectedValuePath="ProductTypeId" SelectedValue="{Binding Data.ProductTypeId, UpdateSourceTrigger=PropertyChanged}" Grid.ColumnSpan="2"/>
<Button Grid.Column="1" Command="{Binding AddProductTypeCommand}" FontSize="12" Padding="0" Height="15" Background="Transparent" Grid.ColumnSpan="2" Margin="251,6,0,6"/>
</Grid>
</StackPanel>
<StackPanel Margin="0,0,13,0" Grid.Column="1">
<TextBlock Text="Signature" Style="{StaticResource StackPanelTextBox}"/>
<TextBox Text="{Binding Data.Signature}"/>
</StackPanel>
<StackPanel Margin="0,0,0,0" Grid.Column="2">
<TextBlock Text="EAN" Style="{StaticResource StackPanelTextBox}"/>
<TextBox MaxLength="13" Text="{Binding Data.Ean}"/>
</StackPanel>
<Grid Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80*" />
<ColumnDefinition Width="20*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0" Grid.Row="0" Margin="0,7,13,10">
<TextBlock Text="Name" Style="{StaticResource StackPanelTextBox}"/>
<TextBox x:Name="tbName" Text="{Binding Data.Name}"/>
</StackPanel>
<StackPanel Grid.Column="1" Grid.Row="0" Margin="0,0,0,0" VerticalAlignment="Center">
<TextBlock Text="Stock"/>
<mah:NumericUpDown Margin="0,3,0,3" Minimum="0" Interval="1" Value="{Binding Data.InStock}" />
</StackPanel>
</Grid>
ProductView.xaml.cs
public ProductView(Product product)
{
InitializeComponent();
var viewModel = new ProductViewModel(product);
WindowStartupLocation = WindowStartupLocation.CenterScreen;
viewModel.SaveAction = () =>
{
DialogResult = true;
};
viewModel.CancelAction = () =>
{
DialogResult = false;
};
DataContext = viewModel;
}
ProductViewModel
public Action SaveAction { get; set; }
public Action CancelAction { get; set; }
public ICommand Save { get; set; }
public ICommand Cancel { get; set; }
private Product _data;
public Product Data
{
get => _data;
set => SetProperty(ref _data, value);
}
public ProductViewModel(Product data)
{
LoadSellers();
LoadProductTypes();
LoadPackages();
Data = data;
NettoPrice = Data.PurchasePrice;
Save = new DelegateCommand(() => SaveAction?.Invoke());
Cancel = new DelegateCommand(() => CancelAction?.Invoke());
HideData = new DelegateCommand(HideMethod);
AddProductTypeCommand = new DelegateCommand(AddProductType);
EyeColor = #"..\Resources\Images\Eye-grey-48.png";
}
You need an extra data layer if you want to implement edit and cancel behavior of a data model. You can do this by implementing the IEditableObject interface.
You should never open the dialog from the view model. Instead, open it from a Button.Click handler in the code-behind. Define a DataTemplate for the dialog and assign it to the Window.ContentTemplate property. Also make sure that the database handling is implemented inside the model.
The following example shows how to display a reusable EditDialog (that operates on IEditableObject implementations) from the view. The example also shows how to cancel or commit data changes to the data model.
App.xaml
<DataTemplate DataType="{x:Type local:Product}">
<TextBox Text="{Binding Signature}" />
</DataTemplate>
EditDialog.xaml
Reusable dialog. Simply define a DataTemplate for the Window.ContentTemplate property to change the hosted Content.
Assign the data to be edited to the EditDialog.DataContext. This dialog can host any data that implements IEditableObject.
<Window Content="{Binding}">
<Window.Template>
<ControlTemplate TargetType="Window">
<Grid Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<AdornerDecorator Grid.Row="0">
<ContentPresenter />
</AdornerDecorator>
<!-- Dialog chrome -->
<Grid Grid.Row="1">
<StackPanel Orientation="Horizontal">
<Button Content="Commit"
Click="OnOkButtonClicked" />
<Button Content="Cancel"
Click="OnCancelButtonClicked"
IsCancel="True"
IsDefault="True" />
</StackPanel>
<ResizeGrip x:Name="WindowResizeGrip"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Visibility="Collapsed"
IsTabStop="false" />
</Grid>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ResizeMode"
Value="CanResizeWithGrip">
<Setter TargetName="WindowResizeGrip"
Property="Visibility"
Value="Visible" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Template>
</Window>
EditDialog.xaml.cs
public partial class EditDialog : Window
{
public EditDialog()
{
InitializeComponent();
this.Loaded += OnLoaded;
}
// In case the Window is closed using the chrome button
protected override void OnClosing(CancelEventArgs e)
{
if (this.Content is IEditableObject editableObject)
{
editableObject.CancelEdit();
}
base.OnClosing(e);
}
private void OnLoaded(object sender, EventArgs e)
{
// Content is set via data binding
if (this.Content is IEditableObject editableObject)
{
editableObject.BeginEdit();
}
}
private void OnCancelButtonClicked(object sender, RoutedEventArgs e)
{
if (this.Content is IEditableObject editableObject)
{
editableObject.CancelEdit();
}
this.Close();
}
private void OnOkButtonClicked(object sender, RoutedEventArgs e)
{
if (this.Content is IEditableObject editableObject)
{
editableObject.EndEdit();
}
this.Close();
}
}
MainWindow.xaml
<DataGrid ItemsSource="{Binding ProductsCollectionView}"
SelectedItem="{Binding SelectedItem}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<EventSetter Event="MouseDoubleClick"
Handler="DataGridRow_MouseDoubleClick" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
MainWindow.xaml.cs
partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new ProductListViewModel();
}
private void DataGridRow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var productListViewModel = this.DataContext as ProductListViewModel;
// Alternatively, create a e.g., EditItem dependency property
// and bind it to the DataGrid.SelectedItem
Product editItem = productListViewModel.SelectedItem;
var editDialog = new EditDialog()
{
DataContext = editItem
};
editDialog.ShowDialog();
}
}
Product.cs
class Product : INotifyPropertyChanged, IEditableObject
{
internal class ProductData
{
// Use object.MemberwiseClone to create a shallow copy
public ProductData Clone() => MemberwiseClone() as ProductData;
public string Signature { get; set; }
}
public Product()
{
this.EditData = new ProductData();
this.BackupData = new ProductData();
}
public void BeginEdit()
{
if (this.IsInEditMode)
{
// Consider to throw an exception
return;
}
// Creates a shallow copy.
// If required, use a copy constructor to create a deep copy.
this.BackupData = this.EditData.Clone();
this.IsInEditMode = true;
}
public void CancelEdit()
{
if (!this.IsInEditMode)
{
// Consider to throw an exception
return;
}
this.EditData = this.BackupData;
this.IsInEditMode = false;
// Raise change notification for all public properties
// to undo potential binding changes
OnPropertyChanged(null);
}
public void EndEdit()
{
this.IsInEditMode = false;
}
public string Signature
{
get => this.EditData.Signature;
set
{
this.EditData.Signature = value;
OnPropertyChanged();
}
}
public bool IsInEditMode { get; private set; }
private ProductData BackupData { get; set; }
private ProductData EditData { get; set; }
}
I have an UserControl. At the top, there is a global parameter, bound to a static property in the class MultiSliceCommand. Below, there is a TabControl, populated by a Template and bound to public static ObservableCollection<GroupContainer> groups, also a property in MultiSliceCommand. GroupContainer contains various properties, mainly doubles, ints etc., displayed and editable in textboxes in the TabItems.
When I now change a value in TabItem, the corresponding property in the correct element of groups is set.
However, when I close & reopen the dialog, the all the GroupContainers in groups are reset to their defaults - even the properties not bound at any point to the dialog.
Changes to the global variables (outside of the TabControl) are preserved correctly. Changes to the TabControl are also preserved correctly if I remove the binding to the global variables - in explicit, if I remove the lines <local:MultiSliceCommand x:Key="mutliSliceCommand" /> and <TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource mutliSliceCommand}, Path=Mm_Per_Package}" />
How can I change the bindings to preserve the changes to the global variable as well as the contents of the Tabs when closing & reopening the dialog?
The Xaml File:
<UserControl.Resources>
<DataTemplate x:Key="HeaderTemplate">
<Label Content="{Binding Group_Name}" />
</DataTemplate>
<local:MultiSliceCommand x:Key="mutliSliceCommand" />
<DataTemplate x:Key="ItemTemplate">
<Grid>
<TextBox x:Name="_length" Text="{Binding Path=Length, UpdateSourceTrigger=PropertyChanged, Delay=0}" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox
Header="Global Parameters"
Grid.Row="0"
Grid.Column="0"
>
<Grid Height="Auto" Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource mutliSliceCommand}, Path=Mm_Per_Package}" />
</Grid>
</GroupBox>
<GroupBox
Header="Materials"
Grid.Row="1"
Grid.Column="0"
Grid.ColumnSpan="2"
>
<TabControl x:Name="TabControl1"
HorizontalAlignment="Left"
VerticalAlignment="Top"
ItemTemplate="{StaticResource HeaderTemplate}"
ContentTemplate="{StaticResource ItemTemplate}"
/>
</GroupBox>
<!--
<Button Content="Save settings"
Grid.Row="2"
HorizontalAlignment="Right"
Margin="10,10,0,0"
VerticalAlignment="Top"
Width="75"
Click="Btn_Save" />-->
</Grid>
</ScrollViewer>
The Class MultiSliceCommand
public class MultiSliceCommand
{
public static ObservableCollection<GroupContainer> groups { get; set; }
private static double _mm_per_package { get; set; } = 0;
public static double Mm_Per_Package
{
get { return _mm_per_package; }
set { _mm_per_package = value < 0 ? 0 : value; }
}
public MultiSliceCommand()
{
groups = new ObservableCollection<GroupContainer>
{
new GroupContainer("Group 1"),
new GroupContainer("Group 1"),
new GroupContainer("Group 3")
};
}
}
The class ObjectContainer
public class GroupContainer : INotifyPropertyChanged
{
private double _length { get; set; } = 0;
public double Length
{
get { return _length; }
set { _length = value < 0 ? 0 : value; NotifyPropertyChanged("Min_Vector_Length"); }
}
// Methods
public GroupContainer(string group_name)
{
}
// Helper Stuff
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string sProp)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(sProp));
}
}
}
Ok, fixed it with an (somewhat dirty) hack:
I just outsourced the global variable to its own class, and bind the xaml to this class. In MultiSliceCommand, I use getter / setter on the property to just relay the value from the "isolation class"
Isolation class:
public class xaml_backend_variables
{
private static double _mm_per_package = 0;
public static double Mm_Per_Package
{
get { return _mm_per_package; }
set { _mm_per_package = value < 0 ? 0 : value; }
}
public xaml_backend_variables()
{
}
}
MultiSliceCommand
public static double Mm_Per_Package
{
get { return xaml_backend_variables.Mm_Per_Package; }
set { xaml_backend_variables.Mm_Per_Package = value; }
}
XAML Modifications
....
<local:xaml_backend_variables x:Key="xaml_backend_variables" />
....
<TextBox x:Name="Mm_Per_Package" Text="{Binding Source={StaticResource xaml_backend_variables}, Path=Mm_Per_Package}" />
But now all values are preserved correctly when closing and reopening the dialog.
Still, if someone has an explanation why this happens and what would be the correct / elegant way to solve this, I would like very much to know!
Issue
I have a number of buttons in my WPF Window which when clicked need to change the view on the Window but keep the same ViewModel. Yesterday I tried using ControlTemplate for this but people mentioned I was better using a DataTemplate.
I need the binding to happen Via the ViewModel as well as I need to do some checks to see if the user can access the view.
Code
This is some of the code i started to write but I feel like its incorrect.
Here is the DataTemplate that I have defined in my view in Window.Resources:
<DataTemplate x:Key="panel1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="7*"/>
<ColumnDefinition Width="110*"/>
<ColumnDefinition Width="190*"/>
<ColumnDefinition Width="110*"/>
<ColumnDefinition Width="202*"/>
<ColumnDefinition Width="109*"/>
<ColumnDefinition Width="7*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="74*"/>
<RowDefinition Height="50*"/>
<RowDefinition Height="12*"/>
<RowDefinition Height="39*"/>
<RowDefinition Height="11*"/>
<RowDefinition Height="38*"/>
<RowDefinition Height="5*"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="2" Grid.ColumnSpan="3" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Label Content="Video Set:" Foreground="#e37e6e" Grid.Column="2" Grid.ColumnSpan="3" VerticalAlignment="Center" FontSize="22" HorizontalAlignment="Center"/>
<Image Source="{Binding VideoSet}" Height="25" Width="25" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</StackPanel>
<TextBlock Foreground="#e37e6e" FontSize="12" Text="You currently do not have a video set. Please click the button below to add a video. Please note you will not be able to create an On Demand presentation without a media file selected. " Grid.Row="1" Grid.Column="2" TextWrapping="WrapWithOverflow" TextAlignment="Center" Grid.ColumnSpan="3" />
<Button Style="{StaticResource loginButton}" Command="{Binding ScreenBack}" Foreground="White" Content="Add Video" Grid.Column="3" HorizontalAlignment="Stretch" Grid.Row="3" VerticalAlignment="Stretch" Grid.ColumnSpan="1"></Button>
</Grid>
</DataTemplate>
Then I tried to use a ContentPresenter and bind to the DataTemplate:
<ContentPresenter Content="{Binding}" Grid.Row="3" Grid.RowSpan="1" Grid.ColumnSpan="5"/>
Now I want to be able to bind different DataTemplates to the ContentPresenter Via the ViewModel, can anyone help me with this issue?
EDIT:
I can bind the ContentPresenter to the DataTemplate through the static resource like below:
<ContentPresenter ContentTemplate="{StaticResource panel1}" Content="{StaticResource panel1}" Grid.Row="3" Grid.RowSpan="1" Grid.ColumnSpan="5"/>
The DataTemplate like below:
<DataTemplate x:Key="panel1">
</DataTemplate>
But how can i change the ControlPresenter binding from the ViewModel?
EDIT:
Here is my code cycle:
So here are the two DataTemplates:
<DataTemplate DataType="{x:Type local:ViewModelA}">
<TextBlock Foreground="#e37e6e" FontSize="12" Text="You currently do not have a video set. Please click the button below to add a video. Please note you will not be able to create an On Demand presentation without a media file selected. " Grid.Row="1" Grid.Column="2" TextWrapping="WrapWithOverflow" TextAlignment="Center" Grid.ColumnSpan="3" />
</DataTemplate>
<DataTemplate DataType="{x:Type local:ViewModelB}">
<TextBlock Foreground="#e37e6e" FontSize="12" Text="NEWWWWWWWWWWYou" Grid.Row="1" Grid.Column="2" TextWrapping="WrapWithOverflow" TextAlignment="Center" Grid.ColumnSpan="3" />
</DataTemplate>
The my ContentControl:
<ContentControl Content="{Binding SelectedViewModel}" />
I defined my DataContext in the code behind:
WizardViewModel _wizardViewModel = new WizardViewModel();
this.DataContext = _wizardViewModel;
In the WizardViewModel i have:
namespace Podia2016.ViewModels
{
public class WizardViewModel : INotifyPropertyChanged
{
public object SelectedViewModel { get; set; }
ViewModelA s = new ViewModelA();
ViewModelB d = new ViewModelB();
public WizardViewModel()
{
SelectedViewModel = s;
OnPropertyChanged("SelectedViewModel");
}
//BC - BINDS TO CHANGE LECTURE.
public ICommand Next
{
get { return new DelegateCommand<object>(Next_Click); }
}
private void Next_Click(object obj)
{
SelectedViewModel = d;
OnPropertyChanged("SelectedViewModel");
}
}
public class ViewModelA : INotifyPropertyChanged
{
//BC - DEFAULT ONPROPERTYCHANGED EVENT.
public event PropertyChangedEventHandler PropertyChanged;
public ViewModelA()
{
}
/// <summary>
/// This is the standard OnPropertyChanged Event Method
/// </summary>
/// <param name="name"></param>
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
public class ViewModelB : INotifyPropertyChanged
{
//BC - DEFAULT ONPROPERTYCHANGED EVENT.
public event PropertyChangedEventHandler PropertyChanged;
public ViewModelB()
{
}
/// <summary>
/// This is the standard OnPropertyChanged Event Method
/// </summary>
/// <param name="name"></param>
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
}
}
Data templating is much simpler to use (compared to your attempt):
create sub viewmodels, one for each sub view you want to have
// could have base class, may have to implement INotifyPropertyChanged, etc.
public class ViewModelA { }
public class ViewModelB
{
public string SomeProperty { get; }
}
...
public object SelectedViewModel { get; set; }
define data templates
<SomeContainer.Resources>
<!-- using user control -->
<DataTemplate DataType="{x:Type local:ViewModelA}">
<local:UserControlA />
</DataTemplate>
<!-- or like this -->
<DataTemplate DataType="{x:Type local:ViewModelB}">
<Grid>
<TextBlock Text="{Binding SomeProperty}" />
<Button .../>
...
</Grid>
</DataTemplate>
Bind content control to a property which select sub viewmodel
<ContentControl Content="{Binding SelectedViewModel}" />
control what to display by changing selected sub viewmodel:
var a = new ViewModelA();
var b = new ViewModelB() { SomeProperty = "Test" };
// display a - will display UserControlA content in ContentControl
SelectedViewModel = a;
OnPropertyChanged(nameof(SelectedViewModel));
// display b - will display text and button in ContentControl
SelectedViewModel = b;
OnPropertyChanged(nameof(SelectedViewModel));
Store DataTemplate as property of your ViewModel. Access the DataTemplate from ResourceDictionary to store in your property.
Bind <ContentPresenter Content="{Binding}" ContentTemplate="{Binding template1}" .../>
How to access ResourceDictionary from code :
If you have in your WPF project an ResourceDictionary that you use to define resources you can create an instance of it from code like this:
ResourceDictionary res = Application.LoadComponent(
new Uri("/WpfApplication1;component/MyDataTemplateCollection.xaml",
UriKind.RelativeOrAbsolute)) as ResourceDictionary;
Where WpfApplication1 is name of your assembly and MyDataTemplateCollection.xaml is name of your ResourceDictionary.
Another way is to use the code-behind for the resource dictionary.
Add x:Class to your ResourceDictionary:
Add class MyDataTemplateCollection.xaml.cs as your code-behind for the ResourceDictionary.
The code behind class looks like so:
partial class MyDataTemplateCollection: ResourceDictionary
{
public MyDataTemplateCollection()
{
InitializeComponent();
}
}
Usage :
ResourceDictionary res = new MyDataTemplateCollection();
I wrote user control with 2 buttons and one check box and now I want to bind Commands to data context - for each button and checkbox.
But I don't know how to define command binding. I think I'll need some kind of ICommand property in User control - but how can I connect user's data context command delegate? I want to use user control to manage each item in collection like this:
<ItemsControl ItemsSource="{Binding Path=MoneyInfo}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:ChannelSetupControl
CurrentCount="{Binding Count}"
CoinValue="{Binding Value}"
UpCommand="{Binding DataContextUp}"
DownCommand="{Binding DataContextDown}"
ChangeCheckboxCommand="{Binding DataContextChange}"></local:ChannelSetupControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
XAML User control
<UserControl>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="3*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="0" Text="{Binding CoinValue}" TextAlignment="Center"></TextBlock>
<TextBlock Grid.Column="0" Grid.Row="1" Text="{Binding CurrentCount, Mode=TwoWay}" TextAlignment="Center" VerticalAlignment="Center" FontSize="30"></TextBlock>
<StackPanel Grid.Column="1" Grid.Row="1" VerticalAlignment="Center">
<Button Content="+ 10" Padding="0 5"></Button>
<Button Content="- 10" Padding="0 5"></Button>
</StackPanel>
<CheckBox Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="2" IsChecked="{Binding Cycling, Mode=TwoWay}" Content="recycling" VerticalContentAlignment="Center"></CheckBox>
</Grid>
</UserControl>
and code behind and this is where I'm lost - how to define UpCommand, DownCommand and ChangeCheckboxCommand?
public partial class ChannelSetupControl : UserControl, INotifyPropertyChanged
{
private int currentCount;
private bool cycling;
private double coinValue;
public int Step { get; set; }
public double CoinValue { get { return coinValue; } set { coinValue = value; NotifyPropertyChanged("CoinValue"); } }
public int CurrentCount { get { return currentCount; } set { currentCount = value; NotifyPropertyChanged("CurrentCount"); } }
public bool Cycling { get { return cycling; } set { cycling = value; NotifyPropertyChanged("Cycling"); } }
public ChannelSetupControl()
{
InitializeComponent();
DataContext = this;
CurrentCount = 0;
Step = 10;
Cycling = false;
CoinValue = 0;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
First of all your ChannelSetupControl class extends UserControl, so it implicitly extends DependencyObject class. It means you can use Dependency Properties instead of implementing INotifyPropertyChanged.
So you can define a dependency property in your ChannelSetupControl class, like this one:
public static readonly DependencyProperty UpCommandProperty =
DependencyProperty.Register("UpCommand", typeof(ICommand), typeof(ChannelSetupControl));
public ICommand UpCommand
{
get { return (ICommand)GetValue(UpCommandProperty); }
set { SetValue(UpCommandProperty, value); }
}
At the same time in your control XAML:
<Button Command="{Binding RelativeSource={RelativeSource Mode=Self}, Path=UpCommand, Mode=OneWay}"
Content="+ 10" Padding="0 5" />
In this way in your window XAML you can wrote:
<local:ChannelSetupControl UpCommand="{Binding UpCommand, Mode=OneWay}" ... />
You can use the same "pattern" for the other controls.
Regarding ICommand, there are a lot of implementations. The one that I prefer is the so called delegate command (for a sample you can take a look here).
I hope this quick explanation can help you.
Can somebody explain to me how exactly to create a ViewModel for the MVVM Pattern.
I tried to understand the the tutorial here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx , but I was unable to understand what exactly is happening in the code.
Let's say we want to create a basic application about getting and adding people from and to a local database and displaying them in the View. How should the ViewModel look like and how to create the RelayCommands for it. First why do we set the variables twice: once privately and then again publicaly.
EDIT: Thanks for the help so far. I have one more thing that I don't know to do - how to bind the View to the ViewModel and Vice Versa
Here is the Model:
public class Student : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private string name;
private string surname;
private string age;
public string Name
{
get
{
return name;
}
set
{
name = value;
OnPropertyChanged("Name");
}
}
public string Surname
{
get
{
return surname;
}
set
{
surname = value;
OnPropertyChanged("Surname");
}
}
public string Age
{
get
{
return age;
}
set
{
age = value;
OnPropertyChanged("Age");
}
}
}
and here is the ViewModel:
public class MainViewModel : ViewModelBase
{
ObservableCollection<Student> studentList;
Student selectedPerson;
public MainViewModel()
{
//populate some sample data
studentList = new ObservableCollection<Student>()
{
new Student(){Name="John", Surname="Smith", Age="28"},
new Student(){Name="Barbara", Surname="Anderson", Age="23"}
};
}
public ObservableCollection<Student> StudentList
{
get { return studentList; }
}
public Student SelectedPerson
{
get { return selectedPerson; }
set
{
selectedPerson = value;
RaisePropertyChanged("SelectedPerson");
}
}
private RelayCommand _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand(() =>
{
Student student = new Student();
studentList.Add(student);
}));
}
}
}
I have found a way to bind the ViewModel to the View using some code for the view in Csharp but the question how to bind the View to the ViewModel is still on my mind. To be more specific how to create a new student using the values a user has entered in the View.
Here is the View's XAML code
<Window x:Class="MVVMLight.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
SizeToContent="WidthAndHeight">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="2*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="NameTextBlock"
Text="Name"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock x:Name="SurnameTextBlock"
Grid.Row="1"
Text="Surname"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock x:Name="AgeTextBlock"
Grid.Row="2"
Text="Age"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBox x:Name="NameTextBox"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<TextBox x:Name="SurnameTextBox"
Grid.Row="1"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<TextBox x:Name="AgeTextBox"
Grid.Row="2"
Grid.Column="1"
Style="{StaticResource TextBoxTextStyle}"/>
<ListBox x:Name="StudentListBox"
Grid.ColumnSpan="2"
Grid.Row="4"
Style="{StaticResource ListBoxStyle}"
ItemsSource="{Binding StudentList}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Name}"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock Text="{Binding Surname}"
Grid.Column="1"
Style="{StaticResource TextBlockTextStyle}"/>
<TextBlock Text="{Binding Age}"
Grid.Column="2"
Style="{StaticResource TextBlockTextStyle}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button x:Name="AddButton"
Grid.Row="7"
Grid.ColumnSpan="2"
HorizontalAlignment="Center"
Content="Add"
Margin="7,7,7,7"
Command="{Binding AddStudentCommand}"/>
</Grid>
And here is the View's Csharp code
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
I have some questions concerning the Binding between the View and The ViewModel:
What are the pros and cons of using this type of binding?
What is the best way of binding if I am going to use a database?
Is this how the ViewModel and Model should look like
How to create a RelayCommand for adding a student to the ObservableCollection
Why do we set things first privately and then again publically [Answered]
How to bind the View to the ViewModel and Vice Versa
in your property setters you should check to see if the new value is equal to the old value, if it is you should return and not fire the PropertyChanged event.
As for your questions:
Yes this looks fine.
There are a couple of ways to setup your relay commands. I prefer
private RelayCommand<Student> _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand<Student>((student) =>
{
studentList.Add(student);
}));
}
}
another way without passing in a student object
private RelayCommand _addStudentCommand;
public ICommand AddStudentCommand
{
get
{
return _addStudentCommand
?? (_addStudentCommand = new RelayCommand(() =>
{
Student student = new Student();
studentList.Add(student);
}));
}
}
That is how properties work in .net, You could use automatic properties, but since you need to fire change notification in the setter you have to declare the field that the property will work against.
Also since it looks like you are using mvvm light you should try the code snippets. They make properties very easy to create. type mvvvminpc then hit tab twice. then fill in the highlighted part and hit tab till you are finished.
You can bind the View To the Viewmodel a couple of ways. I know that it is an Antipattern but you could use a locator. The basic idea is to set the viewmodel as the views datacontext.
public class Locator
{
public Viewmodel1 Viewmodel1
{
return new Viewmodel1();
}
}
You then in you app.xaml you add this class
<Application.Resources>
<Locator x:key="VMLocator" />
</Application.Resources>
Then in your view in the xaml
<Page DataContext="{Binding Source="{StaticResource VMLocator}" Path=ViewModel1}">
</Page>