How to create a generic view model - wpf

I have to create a generic viewmodel passing an entity with a one to many relationship.
I'll explain:
My WIndows:
<Window x:Class="Invoice_Example_Brux.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:invoiceExampleBrux="clr-namespace:Invoice_Example_Brux"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<invoiceExampleBrux:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<TextBox HorizontalAlignment="Left" Height="23" Margin="174,78,0,0" TextWrapping="Wrap" Text="{Binding MyModel.Name}" VerticalAlignment="Top" Width="120"/>
<Label Content="Id" HorizontalAlignment="Left" Margin="10,53,0,0" VerticalAlignment="Top"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="10,78,0,0" TextWrapping="Wrap" Text="{Binding MyModel.Id}" VerticalAlignment="Top" Width="120" IsReadOnly="True"/>
<Label Content="Number" HorizontalAlignment="Left" Margin="322,52,0,0" VerticalAlignment="Top"/>
<TextBox HorizontalAlignment="Left" Height="23" Margin="322,78,0,0" TextWrapping="Wrap" Text="{Binding MyModel.Number}" VerticalAlignment="Top" Width="120"/>
<Label Content="Name" HorizontalAlignment="Left" Margin="174,53,0,0" VerticalAlignment="Top"/>
<Button Content="Save" HorizontalAlignment="Left" Margin="211,288,0,0" VerticalAlignment="Top" Width="75" Command="{Binding SaveCommand}"/>
<ComboBox
SelectionChanged="Selector_OnSelectionChanged"
HorizontalAlignment="Left" Margin="180,38,0,0" VerticalAlignment="Top" Width="120"
DisplayMemberPath="Name"
ItemsSource="{Binding DocumentType,UpdateSourceTrigger=PropertyChanged}"/>
<Label Content="Type Document" HorizontalAlignment="Left" Margin="192,12,0,0" VerticalAlignment="Top"/>
</Grid>
myWindows codebheind:
namespace Invoice_Example_Brux
{
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var cmb = sender as ComboBox;
var selectedItem = cmb.SelectedValue as DocumentType;
if (selectedItem == null) return;
var code = selectedItem.Code;
switch (code)
{
case "A":
DataContext = new ViewModelGeneric<DocumentA>();
break;
case "B":
DataContext = new ViewModelGeneric<DocumentB>();
break;
case "C":
break;
}
}
}
}
My Entity DocumentA and DocumentB:
public class DocumentA : DocumentGeneral
{
public ObservableCollection<DetailDocumentA> DetailDocumentA { get; set; }
}
public class DetailDocumentA : DetailDocumentGeneral
{
}
public class DocumentB : DocumentGeneral
{
public ObservableCollection<DetailDocumentB> DetailDocumentB { get; set; }
}
public class DetailDocumentB : DetailDocumentGeneral
{
}
public class DocumentGeneral
{
public Guid Id { get; set; }
public string Name { get; set; }
public string Number { get; set; }
public string TypeDocument { get; set; }
}
public class DetailDocumentGeneral
{
public Guid Id { get; set; }
public string Quantity { get; set; }
public string Price { get; set; }
public string Total { get; set; }
}
My ViewModelGeneric:
public class ViewModelGeneric<T> : ViewModelBase
where T : DocumentGeneral, new()
{
public T MyModel { get; set; }
public RelayCommand SaveCommand { get; set; }
public ViewModelGeneric()
{
MyModel = new T();
SaveCommand = new RelayCommand(Save);
}
private void Save(object obj)
{
if (MyModel.Id == Guid.Empty)
{
MyModel.Id = Guid.NewGuid();
}
//the problme is here. how can I do to fill in the detail of my entity
/* MyModel.Detail.Add(new DetailDocumentGeneral
{
Price = "50",Quantity = "100",Total = "5000"
});*/
using (var ctx = new DocumentContext())
{
var document = ctx.Set<T>();
document.Add(MyModel);
ctx.SaveChanges();
}
}
}
the problem is that depending on the choice of my combobox I have to go a different entity.
But I can not access the respective detailDocument = (

Please stop what you are doing... WPF is a verbose language. That means that you'll have to write loads of code, probably even duplicating large sections. Please just do yourself a favour and accept this fact. I've seen this situation time and time again (and even wasted my own time on it when I was first learning WPF), but it always ends in tears.
In MVVM, it is customary to provide one view model for one view. This view model is responsible for providing all of the data and functionality for the related view. Unless all of your views are almost identical, then your generic view model simply won't work. Worse still, even if it did work, it would complicate the hell out of your project and make simple tasks more difficult.
If you really don't want to rewrite your view models each time, then just copy and paste from another view model and just change the differences. Having said all that, I must say that your code seems to be somewhat at odd with your words... you say that you want a generic view model, but your problems relates to data access.
It is far more usual to have your data access class(es) in a separate project to your view models. In this respect, it seems as though you are trying to implement The Repository Pattern, but in the wrong place. Making generic data access classes is far more common and has far less to go wrong than a generic view model.
Therefore, I would strongly recommend that you forget about generic view models and declare a new one for each view and then move your data access code into a separate project (or at least a separate class) and optionally make that generic instead (using the linked page on MSDN for help). This may be the first real problem that you have come across in this endeavour, but if you continue along this path, I can guarantee you that it won't be the last.

Related

WPF listbox binding to List<string> or List<string[]>

I'm very new to WPF (especially XAML) *
Hello, my app has a class which gets bunch of comma separated string. So I made a List collection to store the strings. Also, I made DataTemplate for the listbox. Here is code.
MainWindow.xaml
...
<DataTemplate x:Key="DataTemplate">
<Grid>
<StackPanel Grid.Column="1" Margin="5">
<StackPanel Orientation="Horizontal" TextBlock.FontWeight="Bold" >
<TextBlock Text="{Binding AAA}" />
</StackPanel>
<TextBlock Text="{Binding BBB}" />
<TextBlock Text="{Binding CCC}" />
</StackPanel>
</Grid>
</DataTemplate>
...
<ListBox x:Name="listbox1" HorizontalAlignment="Left" Height="309" Margin="10,10,0,0" Width="216" BorderThickness="1" VerticalAlignment="Top" ItemTemplate="{DynamicResource HeadlineDataTemplate}"/>
MainWindow.xaml.cs
...
MyClass myClass;
public MainWindow()
{
InitializeComponent();
myClass = new MyClass();
}
...
private void Button_Click(object sender, RoutedEventArgs e)
{
myClass.getData(Textbox1.Text); // I want to convert this to add items to listbox1.
}
MyClass.cs
...
public void getData(string target)
{
List<string> itemList = new List<string>();
...
while(result != null)
{
// This loop gives bunch of comma separated string (more than 100)
itemList.Add(result);
}
// after the loop, itemList has
// itemList[0] = {"AAA1,BBB1,CCC1"}
// itemList[1] = {"AAA2,BBB2,CCC2"}
// itemList[2] = {"AAA3,BBB3,CCC3"}
// ...
// I also can store the strings to List<string[]> using string.split().
}
So how should I do this?
I couldn't find the answer on the internet.
I suggest to create a model class to represent each ListBox Item. In this example I name it MyListBoxItemModel :
public class MyListBoxItemModel
{
public string AAA { get; set; }
public string BBB { get; set; }
public string CCC { get; set; }
}
Then in the getData function, create List of MyListBoxItemModel to be listbox1's ItemsSource :
public void getData(string target)
{
List<MyListBoxItemModel> itemList = new List<MyListBoxItemModel>();
...
while(result != null)
{
var splittedResult = result.Split(',');
itemList.Add(new MyListBoxItemModel{
AAA = splittedResult[0],
BBB = splittedResult[1],
CCC = splittedResult[2]
});
}
listbox1.ItemsSource = itemList;
}
Note that this example only demonstrates minimum amount of code needed to get your data displayed in the ListBox. Not involving various techniques and best practices around WPF development, such as implementing INotifyPropertyChanged interface, MVVM pattern, etc.
you can just bind to public properties, so if you write something like
<TextBlock Text="{Binding BBB}" />
you need an object with a public property "BBB".
so in addition to the answer from har07 you should also use a public property for your list(OberservableCollection)
public OberservableCollection<MyListBoxItemModel> ItemList {get;set;}
then your binding for your itemscontrol can look like this
<ListBox ItemsSource="{Binding ItemList}" ItemTemplate="{DynamicResource HeadlineDataTemplate}"/>
all you need now is the right datacontext ;)

compose new model objects[recursively] of a complex model object(inside viewmodel), from properties of view in MVVM WPF

I have view class which is called Client, its view model is ClientViewModel. ClientViewModel is has a model object ClientInfo. This ClientInfo [Model] is complex object, which has properties of Model classes called Client & ClientProfile.
I have bound the properties of my UI elements in View, like as follows, (I use xxx.yyy.zzz to get to the property)
<Label Content="First Name:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3,5,0,4" VerticalAlignment="Center" Height="26" Width="70" />
<TextBox Grid.Column="1" Grid.Row="1" Height="24" HorizontalAlignment="Left" Margin="3,7,0,4" Name="firstNameTextBox" Text="{Binding Path=ClientInfo.Client.FirstName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
<Label Content="Last Name:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" Margin="3,3,0,6" VerticalAlignment="Center" Height="26" Width="69" />
<TextBox Grid.Column="1" Grid.Row="2" Height="24" HorizontalAlignment="Left" Margin="3,5,0,6" Name="lastNameTextBox" Text="{Binding Path=ClientInfo.Client.LastName, Mode=TwoWay, ValidatesOnExceptions=true, NotifyOnValidationError=true}" VerticalAlignment="Center" Width="120" />
..
<Button Content="Save" Height="24" Grid.Column="0" Grid.Row="0" Command="{Binding SubmitCommand}" Cursor="Hand" Margin="549,10,10,0" Name="button1" VerticalAlignment="Top" Width="75" RenderTransformOrigin="-0.137,-1.804" />
ClientViewModel:
[Export(typeof(ClientViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ClientViewModel : NotificationObject
{
private readonly IClientService clientService;
private ClientInfo clientInfoModel;
private string currentState;
public DelegateCommand<object> SubmitCommand { get; private set; }
public DelegateCommand<object> UpdateCommand { get; private set; }
public DelegateCommand<object> LoadCommand { get; private set; }
[Import]
public ClientInfo ClientInfoModel
{
get { return this.clientInfoModel; }
set
{
clientInfoModel = value;
this.RaisePropertyChanged(() => this.ClientInfoModel);
}
}
[ImportingConstructor]
public ClientViewModel(IClientService clientService)
{
this.clientService = clientService;
this.SubmitCommand = new DelegateCommand<object>(this.Submit);
this.UpdateCommand = new DelegateCommand<object>(this.Update);
this.LoadCommand = new DelegateCommand<object>(this.Load);
}
private void Load(object obj)
{
throw new NotImplementedException();
}
private void Update(object obj)
{
//update
throw new NotImplementedException();
}
private void Submit(object obj)
{
string s = this.ClientInfoModel.ClientBasic.FirstName;//<--- this where i get the NPE exception
}
public string ViewName
{
get { return "Client Details"; }
}
public string CurrentState
{
get
{
return this.currentState;
}
set
{
if (this.currentState == value)
{
return;
}
this.currentState = value;
this.RaisePropertyChanged(() => this.CurrentState);
}
}
public bool CanSubmit
{
get { return true; }
}
public void Submit()
{
this.CurrentState = "Submitting";
//this.clientRepository.SaveClientAsync(this.ClientInfoModel, result => { SaveClient(); });
}
private object SaveClient()
{
this.CurrentState = "Saving";
return null;
}
}
ClientInfo (Model):
public class ClientInfo : DomainObject
{
public Client ClientBasic { get; set; }
public ClientProfile Profile { get; set; }
}
Client (Model):
public class Client : DomainObject
{
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
....
}
In submit command invocation:
private void Submit(object obj)
{
ClientInfo ci = new ClientInfo();
ci.Client <-- (here i would want to get the new Client obj assigned from properties?)
ci.ClientProfile <---(same as above)
}
View has submit button to save, on save command. I have to save new client object calling some services.
Issue here is that, I need to fill ClientInfo model with new Client() & new ClientProfile() objects. How can do that with this setup I am having.
I can see a few points here which I would do differently. However, in general with what you've posted everything is fine, there must be some mistake in the bits that you haven't posted. Please post the complete ViewModel class and explain how you pass the M to the VM and the VM to the V and I'll have another look at it.
If ClientInfo is null when Submit(..) is executed, that means that your ViewModel doesn't have a model. There must be something wrong with the assignment of the ClientInfo on your ViewModel. Try to set a breakpoint in ClientInfos set accessor and see whether it is set once and only once. Try to set a breakpoint in, for example, FirstNames set accessor and see whether it gets hit when you enter the name in your UI. Are there any BindingErrors shown in the output console?
That being said, are you sure that you want to do what you are trying at all? If you create a new ClientInfo class, and assign the properties Client and ClientProfile from another ClientInfo class, your two ClientInfo objects point to the exact same Client and ClientProfile object. As ClientInfo only has these two properties, I can think of reason why you would duplicate the ClientInfo object. you can just as good use the original object which is your ViewModel's Model...
Secondly, your ViewModel exposes the Model directly, which is not actually the point of a ViewModel, especially when you end up with chained bindings like
Text="{Binding Path=ClientInfo.Client.FirstName}"
The ViewModels core competency is to aggregate the data and allow easy binding from the View. I would expose a FirstName, LastName, etc. property on the ViewModel and let the ViewModel figure out where to take the data from and push the data to. Remember that you want to keep the View independent of any implementation details in the background.
Maybe these two suggestions already solve or avoid the problem altogether. Otherwise please feel free to post more context and I'll have a look again.
EDIT
In your ViewModel, I would expect something like
ClientInfoModel = clientService.GetClientInfo(...);
You inject the service, but where do you initialize clientInfoModel?

Binding to Complex Objects in the ViewModel from the View?

Say for example I have the following type:
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
The above type can be assigned to be held in a Propety in a ViewModel like so assuming a corresponding backing field has been created but omitted here ofc:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
// raise property changed etc
}
}
In my xaml a straight forward binding would be:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
Text="{Binding MessageToDisplay,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Can you extend a binding by using the dot notation syntax? e.g:
<TextBlock x:Name="StatusMessageTextBlock"
Width="Auto"
Height="Auto"
Style="{StaticResource StatusMessageboxTextStyle}"
**Text="{Binding SelectedSite.Name,**
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
Seems like a an interesting feature but my gut instinct is a no as my DC is being assigned at RunTime so at DesignTime or CompileTime, I can't see any clues that could make this feature work or not?
Correct me if have misunderstood what a complex object is, I have simplified mine down for the sake of this question.
Of course this is possible. However, WPF needs to know when any property along the path has changed. To that end, you need to implement INotifyPropertyChanged (or other supported mechanisms). In your example, both Site and the VM containing SelectedSite should implement change notification).
Here's how you could implement the functionality you specified in your question:
// simple DTO
public class Site
{
public string Name { get; set; }
public int SiteId { get; set; }
public bool IsLocal { get; set; }
}
// base class for view models
public abstract class ViewModel
{
// see http://kentb.blogspot.co.uk/2009/04/mvvm-infrastructure-viewmodel.html for an example
}
public class SiteViewModel : ViewModel
{
private readonly Site site;
public SiteViewModel(Site site)
{
this.site = site;
}
// this is what your view binds to
public string Name
{
get { return this.site.Name; }
set
{
if (this.site.Name != value)
{
this.site.Name = value;
this.OnPropertyChanged(() => this.Name);
}
}
}
// other properties
}
public class SitesViewModel : ViewModel
{
private readonly ICollection<SiteViewModel> sites;
private SiteViewModel selectedSite;
public SitesViewModel()
{
this.sites = ...;
}
public ICollection<SiteViewModel> Sites
{
get { return this.sites; }
}
public SiteViewModel SelectedSite
{
get { return this.selectedSite; }
set
{
if (this.selectedSite != value)
{
this.selectedSite = value;
this.OnPropertyChanged(() => this.SelectedSite);
}
}
}
}
And your view might look something like this (assuming a DataContext of type SitesViewModel):
<ListBox ItemsSource="{Binding Sites}" SelectedItem="{Binding SelectedSite}"/>
Below is what worked for me:
public Site SelectedSite
{
get { return _selectedSite; }
set
{
_selectedSite = value;
RaisePropertyChanged("SelectedSite");
}
}
In my xaml I was able to do:
<TextBox Name="tbSiteName"
Width="250"
Height="30"
Margin="0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
IsReadOnly="True"
Style="{StaticResource MainTextBoxStyle}"
Text="{Binding SelectedSite.Name,
Mode=OneWay,
UpdateSourceTrigger=PropertyChanged}" />
This allows you to access data members off the Site Type without having to create individual properties that wrap each data member on the Site Type. Then individual controls can bind to each property declared in the VM. In a one to one fashion, this aproach can become rather verbose. The binding extension attached to the Text property of the TextBox control shown above, shows that we are not binding to a simple straight forward property but actually to a custom type. Potentially removing the need to create more public properties.

Binding data to ComboBox WPF

I am newbie to WPF, and needs help to bind data into the ComboBox. The xaml file contains the tag below.
<UserControl x:Class="SKAT.Postfordeler.Client.UI.View.ChooseInboxView"
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="42" d:DesignWidth="598">
<Grid>
<StackPanel Orientation="Horizontal">
<ComboBox Name="_currentInbox" Width="180" Margin="5" Height="22" DataContext="{Binding}" />
<Label Content="Et job kører allerede i denne indbakke (1500 ud af 1700 poster behandlet)" Name="_currentProcess" Margin="5" Height="25" />
</StackPanel>
</Grid>
//Inbox class , this class was implemented in seperate project
namespace SKAT.Postfordeler.Shared.DataTypes
{
[DataContract]
public class Inbox
{
[DataMember]
public String Id { get; set; }
[DataMember]
public String Folder { get; set; }
[DataMember]
public Rule Rules { get; set; }
}
}
//This code is located in the controller, the Activate method will fire when the MainWindow was executed
public void Activate()
{
var configuration = _configurationManager.GetConfiguration();// this method gets the xaml file settings
_chooseInboxView.FillInboxes(configuration.Inboxes); // Inboxes data binds to combobox
}
and in the View code behind, I created a method to bind the data which contains a type of list
public void FillInboxes(List<Inbox> inboxes)
{
DataContext = inboxes;
}
But it won't works,Any help please?
I assume your Inbox class consists of two properties (for simplicity), but there may be any number of them:
public class Inbox
{
public int ID { get; set; }
public string Text { get; set; }
}
You write a DataTemplate, for example:
<Grid.Resources>
<DataTemplate x:Key="InboxTemplate">
<WrapPanel>
<TextBlock Text="{Binding Path=ID}"/>
<TextBlock>:</TextBlock>
<TextBlock Text="{Binding Path=Text}"/>
</WrapPanel>
</DataTemplate>
</Grid.Resources>
Then correct your ComboBox declaration like:
<ComboBox Name="_currentInbox" Width="180" Margin="5" Height="22" ItemsSource="{Binding}" ItemTemplate="{StaticResource InboxTemplate}" />
Finally you set DataContext of your ComboBox to your List<Inbox>:
public void FillInboxes(List<Inbox> inboxes)
{
_currentInbox.DataContext = inboxes;
}
EDIT: As you've asked for a simpler solution, you can just override ToString() method of your Inbox class:
protected override string ToString()
{
return ID.ToString() + ":" + Text;
}
Instead of DataContext={Binding} you should have ItemsSource={Binding}.
The data context for any frameworkelement in the visual tree is by default {Binding}.
<ComboBox Name="_currentInbox"
SelectedItem="Hoved"
Width="180"
Margin="5"
Height="22"
DisplayMemberPath="Name"
ItemSource="{Binding}" />
Also for the combobox to display text of the items correctly I suppose you need DisplayMemberPath too. I assumed the property from Inbox class that you need to display is Name. Please replace with your relevant property name.
If your Inbox class is like,
public class Inbox
{
public int ID { get; set; }
public string Text { get; set; }
}
And if you do not want to change your xmal, the code behind method should be like this,
public void FillInboxes(List<Inbox> inboxes)
{
_currentInbox.DisplayMemberPath = "Text"; // To display the 'Text' property in the combobox dropdown
//_currentInbox.DisplayMemberPath = "ID"; // To display the 'ID' property in the combobox dropdown
_currentInbox.DataContext = inboxes;
}

WPF Data binding issue using mvvm pattern

I have created a user control "SearchControl"(which will be reused further in other screens as well.
SearchControl ->
<usercontrol name="SearchControl"......>
<stackpanel orientation="horizontal"...>
<TextBox Text"{Binding Path=UserId}"...>
<Button Content="_Search" ....Command="{Binding Path=SearchCommand}"..>
</stackpanel>
</usercontrol>
public partial class SearchControl : UserControl
{
public SearchControl()
{
InitializeComponent();
DataContext=new UserViewModel();
}
}
I then use this control in a window "UserSearch"
<window name="UserSearch".............
xmlns:Views="Namespace.....Views">
<Grid>
<Grid.RowDefinitions>
<RowDefinition..../>
<RowDefinition..../>
<RowDefinition..../>
<RowDefinition..../>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition..../>
<ColumnDefinition..../>
</Grid.ColumnDefinitions>
<Views:SearchControl Grid.Row="0" Grid.Colspan="2"/>
<TextBlock Text="User Id" Grid.Row="1" Grid.Column="0"..../>
<TextBox Text="{Binding Path=UserId}" Grid.Row="1" Grid.Column="1".../>
<TextBlock Text="First Name" Grid.Row="2" Grid.Column="0"..../>
<TextBox Text="{Binding Path=FirstName}" Grid.Row="2" Grid.Column="1".../>
<TextBlock Text="Last Name" Grid.Row="3" Grid.Column="0"..../>
<TextBox Text="{Binding Path=LastName}" Grid.Row="3" Grid.Column="1".../>
</Grid>
</window>
public partial class UserSearch : Window
{
public UserSearch()
{
InitializeComponent();
DataContext=new UserViewModel();
}
}
What I am aimimg for:
When I enter UserId inthe textbox in SearchControl and click on Search button, the resulting record which is retieved should be displayed in the textboxes for UserId, FirstName, LastName
class UserViewModel:INotifyPropertyChanged
{
DBEntities _ent; //ADO.Net Entity set
RelayCommand _searchCommand;
public UserViewModel()
{
_ent = new DBEntities();
}
public string UserId {get; set;}
public string FirstName {get; set;}
public string LastName {get; set;}
public ICommand SearchCommand
{
get
{
if(_searchCommand == null)
{
_searchCommand = new RelayCommand(param = > this.Search());
}
return _searchCommand;
}
}
public void Search()
{
User usr = (from u in _ent
where u.UserId = UserId
select u).FirstOrDefault<User>();
UserId = usr.UserId;
FirstName = usr.FirstName;
LastName = usr.LastName;
OnPropertyChanged("UserId");
OnPropertyChanged("FirstName");
OnPropertyChanged("LastName");
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertChangedEventArgs(propertyName);
}
}
Here as I am using two separate instances of the UserViewModel for the SearchControl and UserSearch, even though I retieve the record for the particular user on searching by UserId, I am unable to bind the properties UserId, FullName , LastName with the respective textboxes...How do I fix this problem??
1) Don't let the View initialize the presentation model, it should be the other way round. The presentation model is the object of interest, not the particular view.
public interface IView
{
void SetModel(IPresentationModel model);
}
publiv class View : UserControl, IView
{
public void SetModel(IPresentationModel model)
{
DataContext = model;
}
}
public class PresentationModel : IPresentationModel
{
public PresentationModel(IView view)
{
view.SetModel(this);
}
}
2) Don't set the data context of the subview in the code behind file. Usually, the view that uses the subview sets the data context in the xaml file.
3) Usually each view has its own presentation model. The presentation model should have one type of view. That means that different views of a single presentation model may differ in appearance but not in functionality (in your case one view is used to search, the other one is used to display and edit data). So, you have vialoted the Single Responsibilty Principle.
4) Abstract your data access layer, otherwise you won't be able to unit test your presentation model (because it needs access to the data base directly). Define an repository interface and implementation:
public interface IUserRepository
{
User GetById(int id);
}
public class EntityFrameworkUserRepository : IUserRepository
{
private readonly DBEntities _entities;
public EntityFrameworkUserRepository(DBEntities entities)
{
_entities = entities;
}
public User GetById(int id)
{
return _entities.SingleOrDefault(u => u.UserId == id);
}
}
5) Don't use FirstOrDefault because an ID is unique, so there must not be several users for one id. SingleOrDefault (used in the code snippet above) throws an exception if more than one result is found but returns null if none is found.
6) Bind directly to your entity:
public interface IPresentationModel
{
User User { get; }
}
<StackPanel DataContext="{Binding Path=User}">
<TextBox Text="{Binding Path=FirstName}" />
<TextBox Text="{Binding Path=LastName}" />
</StackPanel>
7) Use the CommandParameter to provide the user id you are searching for directly with your command.
<TextBox x:Name="UserIdTextBox">
<Button Content="Search" Command="{Binding Path=SearchCommand}"
CommandParameter="{Binding ElementName=UserIdTextBox, Path=Text}" />
public class PresentationModel
{
public ICommand SearchCommand
{
// DelegateCommand<> is implemented in some of Microsoft.BestPractices
// assemblies, but you can easily implement it yourself.
get { return new DelegateCommand<int>(Search); }
}
private void Search(int userId)
{
_userRepository.GetById(userId);
}
}
8) If only data binding causes issues, look at the following website to get some ideas how to debug wpf data bindings: http://beacosta.com/blog/?p=52
9) Don't use strings that contain property names. Once you refactor your code and properties change their names, to will have a stressful time finding all property names in strings and fixing them. Use lambda expressions instead:
public class PresentationModel : INotifiyPropertyChanged
{
private string _value;
public string Value
{
get { return _value; }
set
{
if (value == _value) return;
_value = value;
RaisePropertyChanged(x => x.Value);
}
}
public PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(Expression<Func<PresentationModel, object>> expression)
{
if (PropertyChanged == null) return;
var memberName = ((MemberExpression)expression.Body).Member.Name;
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
I wish you the best to solve your problem and I hope that I could help you a little bit.
Best Regards
Oliver Hanappi

Resources