I'm using an MVVM structure with EF 6.0 as my data source and a Datagrid as my user control.
I have:
Viewmodels: a base that implements INotifyPropertChanged, a main viewmodel that holds other viewmodels and a "concrete" viewmodel where data is displayed from.
Commands: I have an ICommand class and use that in my viewmodel.
In short it so far correctly displays data in a datagrid. Now I want to perform CRUD operations. However when I make changes to the datagrid and then hit my save button (which is bound to an ICommand property) none of the changes propagate back to the entity.
Here is my viewmodel:
class SymbolWeightsViewModel : ViewModelBase
{
BenchMarkEntities _context = new BenchMarkEntities();
RelayCommand _updateCommand;
public ObservableCollection<Weight> Weights
{
get;
private set;
}
public BenchMarkEntities Context
{
get { return _context; }
}
public SymbolWeightsViewModel()
{
_context.Weights.Load();
this.Weights = _context.Weights.Local;
}
~SymbolWeightsViewModel()
{
_context.Dispose();
}
public ICommand UpdateCommand
{
get
{
if (_updateCommand == null)
{
_updateCommand = new RelayCommand(param => this.UpdateCommandExecute(),
param => this.UpdateCommandCanExecute);
}
return _updateCommand;
}
}
void UpdateCommandExecute()
{
using (_context = new BenchMarkEntities())
{
_context.SaveChanges();
}
}
bool UpdateCommandCanExecute
{
get {return true;}
}
My opinion is BenchmarkEntities object and Datagrid are not seeing each other. Why I'm not sure but somewhere that binding is failing. Do I need to reinstantiate BenchmarkEntities to perform an update?
And my View:
<StackPanel>
<DataGrid ItemsSource="{Binding Weights, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
AutoGenerateColumns="True">
</DataGrid>
<Button Name="SaveChanges" Height="32" Command="{Binding UpdateCommand}">
</Button>
</StackPanel>
View related: I think binding the entire Weight object with the DataGrid is probably not going to work. I need to create and bind to individual columns. Is this true or can you perform updates on a part of an entity via a datagrid?
Really appreciate any help or direction.
Thanks
You create a new Context and then SaveChanges on the new Context which has no pending changes:
void UpdateCommandExecute()
{
using (_context = new BenchMarkEntities())
{
_context.SaveChanges();
}
}
As Mike said:
"You create a new Context and then SaveChanges on the new Context which has no pending changes"
It means you shoud not create new context in function UpdateCommandExecute.
So, try modify your UpdateCommandExecute function as following:
void UpdateCommandExecute()
{
_context.SaveChanges();
}
Happy Coding
Related
I'm pretty new to WPF and MVVM concept and i'm trying to solve probably stupid issue. I have MainViewModel where I control what should be seen on the screen according to pressed buttons in the menu.
class MainViewModel : ObservableObject
{
public RelayCommand HomeViewCommand { get; set; }
public RelayCommand SettingsViewCommand { get; set; }
public HomeViewModel HomeVM { get; set; }
public SettingsViewModel SettingsVM { get; set; }
private object _currentView;
public object CurrentView
{
get { return _currentView; }
set
{
_currentView = value;
OnPropertyChanged();
}
}
public MainViewModel()
{
HomeVM = new HomeViewModel();
SettingsVM = new SettingsViewModel();
CurrentView = HomeVM;
HomeViewCommand = new RelayCommand(o =>
{
CurrentView = HomeVM;
});
SettingsViewCommand = new RelayCommand(o =>
{
CurrentView = SettingsVM;
});
}
One of my Views is SettingsView. In this View I have a button, which should check if the connection string is allright. And because I am going to use kinda lot SQL commands my thought was, to put all code regarding SQL into one folder. So basically the project is MVVM(file)>View(file),ViewModel(file).... and SQL(file)>....
Sadly when I add to the SettingsView sql, the app falls because of 'Object reference not set to an instance of an object'
In the SettingsView is:
public partial class SettingsView : UserControl
{
private readonly HSQLTestConnection _SQLTestConnection;
public SettingsView(HSQLTestConnection SQLTestConnection)
{
InitializeComponent();
_SQLTestConnection = SQLTestConnection;
And button is like this:
_SQLTestConnection.TryConnectionString(ConnectionString);
Model is empty:
class SettingsViewModel
{
public SettingsViewModel()
{
}
}
Interface for SQL is:
public interface HSQLTestConnection
{
void TryConnectionString(string ConnectionString);
}
And SQL function:
public class SQLTestConnection : HSQLTestConnection
{
public void TryConnectionString(string ConnectionString)
{
//do something
}
}
App does not show any error and was working and changing Views pretty fine, I think the issue is with public SettingsView(HSQLTestConnection SQLTestConnection) But I could not find how to solve this.
Since I am going to have multiple classes for SQL, I wanted to solve it this way. It used to work in different app I wrote, but I was not using RellayCommand, which I wanted to try.
Thank you for your help in advance.
I see that SettingsView class extends the UserControl class. This is problematic because
you say you want to follow the MVVM design pattern but your user control code (SettingsView) has a reference to your database connection (HSQLTestConnection) in the constructor. You should not mix your view code with your database code.
your SettingsView is initialized by the framework so where is it supposed to get the value for the HSQLTestConnection parameter in the constructor?
In any case, your view should not have any database logic in it, at least not if you want to follow MVVM.
What you could do is to initiate your database connection in the view model of the settings view SettingsViewModel. Not great, but somewhat better.
Since you mentioned there will be "kinda lot SQL commands", I would suggest you separate this code into a dedicated service, and then use this service in your view models where applicable.
public interface IMyDatabaseService
{
bool TestConnection();
IEnumerable<SomeDataObject> GetData();
...
}
You would then pass this interface where needed:
public class SettingsViewModel : INotifyPropertyChanged
{
private readonly IMyDatabaseService databaseService;
private bool connectionSuccessful;
public RelayCommand TestConnectionCommand { get; set; }
public bool ConnectionSuccessful
{
get => connectionSuccessful;
set
{
connectionSuccessfull = value;
var propertyName = nameof(ConnectionSuccessful);
PropertyChanged?.Invoke(this, new (propertyName));
}
}
public SettingsViewModel(IMyDatabaseService databaseService)
{
this.databaseService = databaseService;
TestConnectionCommand = new RelayCommand(_ =>
ConnectionSuccessful = databaseService.TestConnection()
);
...
}
}
public class MainViewModel
{
...
public MainViewModel()
{
...
IMyDatabaseService databaseService = new MyDatabaseService();
SettingsVM = new SettingsViewModel(databaseService);
...
}
...
}
Notice the TestConnectionCommand as well as the ConnectionSuccesful boolean.
The command will call your database service and this is what you will bind your button to.
Similarly, the boolean can be bound to in your view to show the connection state. Since this value can change whenever you run the command, you have to invoke PropertyChanged to let the framework know the value has changed. In this case, the invocation is done in the setter.
Assuming your data context is set correctly, in your SettingsView view you would now have:
...
<Button Command="{Binding TestConnectionCommand}"> Test </Button>
<TextBlock Text="Connection failed!">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding ConnectionSuccessfull}" Value="True">
<Setter Property="Text" Value="Connection successful!" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
...
I used a text block with a data trigger to show the connection state, but you can use the boolean and the data trigger to do whatever you wish. Maybe show a modal dialog or something.
Better still would be to use some sort of dependency injection to inject your database service and other dependencies as well as making the database code async so that your UI won't freeze, but that will be for the future you to figure out.
I have ObserveCollection (entity) associated with ICollectionView
Everything works fine until I try to delete the entry. After clicking on the 'delete' button, the interface is not updated.
If i set ObserveCollection everything works fine
private ICollectionView _taskview;
public ICollectionView TasksView
{
get { return _taskview; }
set
{
_taskview = value;
OnPropertyChanged("TaskView");
}
}
public ICommand DeleteTask
{
get
{
return new DelegateCommand(() =>
{
_context.Task.Attach(SelectTask);
_context.Task.Remove(SelectTask);
_context.SaveChanges();
Tasks = new ObservableCollection<TaskModel>(_context.Task);
TasksView = CollectionViewSource.GetDefaultView(Tasks);
});
}
}
public HomeViewModel(Window window)
{
this.window = window;
Tasks = new ObservableCollection<TaskModel>(_context.Task);
TasksView = CollectionViewSource.GetDefaultView(Tasks);
}
<ListBox Grid.Row="1" Grid.RowSpan="2" Grid.Column="0"
SelectionMode="Extended"
ItemsSource="{Binding TasksView}"
SelectedItem="{Binding SelectTask}">
</ListBox>
Don't create a new collection after each deletion. This will have negative impact on the performance. This is the reason why you use an ObservableCollection. This way the binding target e.g., a ListBox is able to update the changed items only, instead of recreating/rendering the complete view.
In this this context it also doesn't make sense to expose the data source as ICollectionsView. Rather bind to the ObservableCollection directly.
When the source collection of an ICollectionsView implements INotifyCollectionChanged like ObservableCollection<T> does, then the ICollectionView will automatically update when the source changes.
In this case manipulating the INotifyCollectionChanged collection is sufficient.
When the source collection of an ICollectionsView does not implement INotifyCollectionChanged like List<T>, then the ICollectionView will not automatically update when the source changes.
In this case you must explicitely call ICollectionView.Refresh to force the ICollectionView to update.
Please note that you should never reference any view components in your view model - no exceptions. This eliminates all benefits of MVVM. And it is never necessary, for sure. If your view model requires a reference to a view component that you are designing your code or classes wrong.
To follow this basic and fundamental MVVM design rule you must remove the reference to Window from your HomeViewModel.
You can trigger view behavior by exposing a property on the view model which is the input for a data trigger in the view. Patterns - WPF Apps With The Model-View-ViewModel Design Pattern, The Model-View-ViewModel Pattern.
First Solution (Recommended)
You should bind to the Tasks collection directly.
The moment you need to manipulate the collection's view e.g., to apply a filter retrieve the view using CollectionViewSource.GetDefaultView(Tasks). But don't bind to it.
<ListBox ItemsSource="{Binding Tasks}" />
public HomeViewModel()
{
Tasks = new ObservableCollection<TaskModel>(_context.Task);
Tasks.CollectionChanged += OnTasksChanged;
}
private void OnTasksChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
{
foreach (TaskModel task in e.NewItems)
{
_context.Task.Add(task);
_context.SaveChanges();
}
break;
}
case NotifyCollectionChangedAction.Remove:
{
foreach (TaskModel task in e.OldItems)
{
_context.Task.Attach(task);
_context.Task.Remove(task);
_context.SaveChanges();
}
break;
}
}
}
// Then simply manipulate the 'Tasks' collection
public ICommand DeleteTaskCommand => new DelegateCommand(() => Tasks.Remove(SelectTask));
Second Solution
If you want to bind to the ICollectionView, you don't need the additional ObservableCollection anymore (except you want to maintain two collections and a ICollectionView on every add/move/remove/reset operation). To update the collection's view call ICollectionView.Refresh.
<ListBox ItemsSource="{Binding TasksView}" />
public HomeViewModel()
{
TasksView = CollectionViewSource.GetDefaultView(_context.Task);
}
// Then simply refresh the 'TasksView':
public ICommand DeleteTask => DelegateCommand(
() =>
{
_context.Task.Attach(SelectTask);
_context.Task.Remove(SelectTask);
_context.SaveChanges();
// Update the view
TasksView.Refresh();
});
Call Refresh() on View property of CollectionViewSource to get it refreshed.
You have a typo on:
public ICollectionView TasksView
{
get { return _taskview; }
set
{
_taskview = value;
OnPropertyChanged("TaskView");
}
}
At OnPropertyChanged("TaskView"); it should be OnPropertyChanged("TasksView");
I've created a WPF project for the express purpose of illustrating my project.
Disclaimer: I am using Caliburn.Micro ONLY for using PropertyChangedBase and BindableCollection. Everything else here in this project is boilerplate WPF stuff.
Right, so, I have a class DataItem. It has got just two properties:
an int called Qty, and
a calculated property called Sum, which
is double of Qty.
That's it. For the sake of clarity, here's the code:
public class DataLine : PropertyChangedBase
{
private int qty;
public int Qty
{
get
{
return qty;
}
set
{
if (value == qty)
{
return;
}
qty = value;
NotifyOfPropertyChange();
NotifyOfPropertyChange(() => Sum);
}
}
public int Sum => qty * 2;
public DataLine(int a)
{
Qty = a;
}
}
Now, the ViewModel. It's got
a BindableCollection (which is Caliburn's flavour of ObservableCollection) of DataLine,
an int which will add up all the Sum of DataLines, and
an ICommand.
The ICommand does nothing but trigger NotifyPropertyChanged for the BindableCollection and int.
Here's the code:
class ViewModel: PropertyChangedBase
{
public ViewModel()
{
Data = new BindableCollection<DataLine>
{
new DataLine(1),
new DataLine(2),
new DataLine(4),
new DataLine(6)
};
RefreshCommand = new RefreshCommand(this);
}
private BindableCollection<DataLine> _data;
public BindableCollection<DataLine> Data
{
get
{
return _data;
}
set
{
if (value == _data)
{
return;
}
_data = value;
NotifyOfPropertyChange();
NotifyOfPropertyChange(() => Amount);
}
}
public ICommand RefreshCommand { get; private set; }
public void RefreshAction()
{
NotifyOfPropertyChange(() => Data);
NotifyOfPropertyChange(() => Amount);
}
public int? Amount => Data?.Sum(c => c.Sum);
}
Now, for the View:
It's very simple.
There's a DataGrid, bound to the BindableCollection.
There's a Button, bound to the ICommand.
There's a 'TextBox, bound toAmount`.
Here's the body of the View:
<DockPanel>
<TextBox DockPanel.Dock="Bottom" Text="{Binding Amount, TargetNullValue='', Mode=OneWay}"/>
<Button DockPanel.Dock="Bottom" Command="{Binding RefreshCommand}" Content="Refresh"/>
<DataGrid ItemsSource="{Binding Data, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="True"/>
</DockPanel>
When I run it, I get something like this:
Now comes the tricky part - let me just edit one of the rows.
For sake of clarity, I have added an arrow to show where my cursor was after editing the DataGrid. Check how the Sum of first row is updated, but the Amount at the bottom of the screen remains the same.
Now, when I click Refresh, this happens:
The Amount is now updated properly.
All of which finally brings me to my question: What's wrong with DataGrid? Why is DataGrid not triggering its setter when the rows are edited? And how can I make sure it is triggered?
I find Datagrids and updating meta/aggregate data irritating at times.
Currently i would agree with Clemens that i am not sure you can easily change a BindableCollection to suit your needs.
However, a quick solution to not create a tight couple between your DataLine and ViewModel is using a passable Action. You could always get fancy with some abstraction or generics. But I created a simple edit of your code to show what i mean.
Essentially you create a mechanism such that a ViewModel can pass an Action to an Object. If I were implementing this, I would probably implement some form of Interface or Generic that stops me from having to add an Action Method to each Model. Or at least inherit from a class that has the Notify Action. For clarity and easy reading, ill just do a simple edit.
In Your DataLine add:
//NotifyOfPropertyChange();
//NotifyOfPropertyChange(() => Sum);
NotifyFromParent?.Invoke();
to your Qty setter, and:
public DataLine(int a, System.Action action = null)
{
Qty = a;
NotifyFromParent = action;
}
public System.Action NotifyFromParent;
then in your ViewModel you can go:
Data = new BindableCollection<DataLine>
{
new DataLine(1, () => RefreshAction()),
new DataLine(2, () => RefreshAction()),
new DataLine(4, () => RefreshAction()),
new DataLine(6, () => RefreshAction())
};
Like i said, you could make this much fancier. As far as i have experienced you need to create a mechanism to update aggregate or external data when editing cells in a datagrid, and you desire the update to immediately following said edit.
I'm trying to develop an easy MVVM project that it has two windows:
The first window is a text editor, where I bind some properties such as FontSize or BackgroundColor:
<TextBlock FontSize="{Binding EditorFontSize}"></TextBlock>
its DataContext is MainWindowViewModel:
public class MainWindowViewModel : BindableBase
{
public int EditorFontSize
{
get { return _editorFontSize; }
set { SetProperty(ref _editorFontSize, value); }
}
.....
The second window is the option window, where I have an slider for changing the font size:
<Slider Maximum="30" Minimum="10" Value="{Binding EditorFontSize }" ></Slider>
its DataContext is OptionViewModel:
public class OptionViewModel: BindableBase
{
public int EditorFontSize
{
get { return _editorFontSize; }
set { SetProperty(ref _editorFontSize, value); }
}
.....
My problem is that I have to get the value of the slider in the option window and then I have to modify the FontSize property of my TextBlock with this value. But I don't know how to send the font size from OptionViewModel to MainViewModel.
I think that I should use:
A shared model
A model in MainWindowViewModel and a ref of this model in OptionViewModel
Other systems like notifications, messages ...
I hope that you can help me. It's my first MVVM project and English isn't my main language :S
Thanks
Another option is to store such "shared" variables in a SessionContext-class of some kind:
public interface ISessionContext: INotifyPropertyChanged
{
int EditorFontSize { get;set; }
}
Then, inject this into your viewmodels (you are using Dependency Injection, right?) and register to the PropertyChanged event:
public class MainWindowViewModel
{
public MainWindowViewModel(ISessionContext sessionContext)
{
sessionContext.PropertyChanged += OnSessionContextPropertyChanged;
}
private void OnSessionContextPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "EditorFontSize")
{
this.EditorFontSize = sessionContext.EditorFontSize;
}
}
}
There are many ways to communicate between view models and a lot of points what the point is the best. You can see how it is done:
using MVVMLight
in Prism
by Caliburn
In my view, the best approach is using EventAggregator pattern of Prism framework. The Prism simplifies MVVM pattern. However, if you have not used Prism, you can use Rachel Lim's tutorial - simplified version of EventAggregator pattern by Rachel Lim.. I highly recommend you Rachel Lim's approach.
If you use Rachel Lim's tutorial, then you should create a common class:
public static class EventSystem
{...Here Publish and Subscribe methods to event...}
And publish an event into your OptionViewModel:
eventAggregator.GetEvent<ChangeStockEvent>().Publish(
new TickerSymbolSelectedMessage{ StockSymbol = “STOCK0” });
then you subscribe in constructor of another your MainViewModel to an event:
eventAggregator.GetEvent<ChangeStockEvent>().Subscribe(ShowNews);
public void ShowNews(TickerSymbolSelectedMessage msg)
{
// Handle Event
}
The Rachel Lim's simplified approach is the best approach that I've ever seen. However, if you want to create a big application, then you should read this article by Magnus Montin and at CSharpcorner with an example.
Update: For versions of Prism later than 5 CompositePresentationEvent is depreciated and completely removed in version 6, so you will need to change it to PubSubEvent everything else can stay the same.
I have done a big MVVM application with WPF. I have a lot of windows and I had the same problem. My solution maybe isn't very elegant, but it works perfectly.
First solution: I have done one unique ViewModel, splitting it in various file using a partial class.
All these files start with:
namespace MyVMNameSpace
{
public partial class MainWindowViewModel : DevExpress.Mvvm.ViewModelBase
{
...
}
}
I'm using DevExpress, but, looking your code you have to try:
namespace MyVMNameSpace
{
public partial class MainWindowViewModel : BindableBase
{
...
}
}
Second solution: Anyway, I have also a couple of different ViewModel to manage some of these windows. In this case, if I have some variables to read from one ViewModel to another, I set these variables as static.
Example:
public static event EventHandler ListCOMChanged;
private static List<string> p_ListCOM;
public static List<string> ListCOM
{
get { return p_ListCOM; }
set
{
p_ListCOM = value;
if (ListCOMChanged != null)
ListCOMChanged(null, EventArgs.Empty);
}
}
Maybe the second solution is simplier and still ok for your need.
I hope this is clear. Ask me more details, if you want.
I'm not a MVVM pro myself, but what I've worked around with problems like this is,
having a main class that has all other view models as properties, and setting this class as data context of all the windows, I don't know if its good or bad but for your case it seems enough.
For a more sophisticated solution see this
For the simpler one,
You can do something like this,
public class MainViewModel : BindableBase
{
FirstViewModel firstViewModel;
public FirstViewModel FirstViewModel
{
get
{
return firstViewModel;
}
set
{
firstViewModel = value;
}
}
public SecondViewModel SecondViewModel
{
get
{
return secondViewModel;
}
set
{
secondViewModel = value;
}
}
SecondViewModel secondViewModel;
public MainViewModel()
{
firstViewModel = new FirstViewModel();
secondViewModel = new SecondViewModel();
}
}
now you have to make another constructor for your OptionWindow passing a view model.
public SecondWindow(BindableBase viewModel)
{
InitializeComponent();
this.DataContext = viewModel;
}
this is to make sure that both windows work on the same instance of a view model.
Now, just wherever you're opening the second window use these two lines
var window = new SecondWindow((ViewModelBase)this.DataContext);
window.Show();
now you're passing the First Window's view model to the Second window, so that they work on the same instance of the MainViewModel.
Everything is done, just you've to address to binding as
<TextBlock FontSize="{Binding FirstViewModel.EditorFontSize}"></TextBlock>
<TextBlock FontSize="{Binding SecondViewModel.EditorFontSize}"></TextBlock>
and no need to say that the data context of First window is MainViewModel
In MVVM, models are the shared data store. I would persist the font size in the OptionsModel, which implements INotifyPropertyChanged. Any viewmodel interested in font size subscribes to PropertyChanged.
class OptionsModel : BindableBase
{
public int FontSize {get; set;} // Assuming that BindableBase makes this setter invokes NotifyPropertyChanged
}
In the ViewModels that need to be updated when FontSize changes:
internal void Initialize(OptionsModel model)
{
this.model = model;
model.PropertyChanged += ModelPropertyChanged;
// Initialize properties with data from the model
}
private void ModelPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(OptionsModel.FontSize))
{
// Update properties with data from the model
}
}
I'm new to WPF and I've come up with a solution to this and I'm curious of more knowledgeable people's thoughts about what's right and wrong with it.
I have an Exams tab and a Templates tab. In my simple proof of concept, I want each tab to "own" an Exam object, and to be able to access the other tab's Exam.
I define the ViewModel for each tab as static because if it's a normal instance property, I don't know how one tab would get the actual instance of the other tab. It feels wrong to me, though it's working.
namespace Gui.Tabs.ExamsTab {
public class GuiExam: INotifyPropertyChanged {
private string _name = "Default exam name";
public string Name {
get => _name;
set {
_name = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName="") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public partial class ExamsHome : Page {
public ExamsHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly ExamsTabViewModel ViewModel = new ExamsTabViewModel();
}
public class ExamsTabViewModel {
public GuiExam ExamsTabExam { get; set; } = new GuiExam() { Name = "Exam from Exams Tab" };
public GuiExam FromTemplatesTab { get => TemplatesHome.ViewModel.TemplatesTabExam; }
}
}
namespace Gui.Tabs.TemplatesTab {
public partial class TemplatesHome : Page {
public TemplatesHome() {
InitializeComponent();
DataContext = ViewModel;
}
public static readonly TemplatesTabViewModel ViewModel = new TemplatesTabViewModel();
}
public class TemplatesTabViewModel {
public GuiExam TemplatesTabExam { get; set; } = new GuiExam() { Name = "Exam from Templates Tab" };
public GuiExam FromExamTab { get => ExamsHome.ViewModel.ExamsTabExam; }
}
}
And then everything is accessible in the xaml:
TemplatesHome.xaml (excerpt)
<StackPanel Grid.Row="0">
<Label Content="From Exams Tab:"/>
<Label FontWeight="Bold" Content="{Binding FromExamTab.Name}"/>
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="Local Content:"/>
<TextBox Text="{Binding TemplatesTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>
ExamsHome.xaml (excerpt)
<StackPanel Grid.Row="0">
<Label Content="Local Content:"/>
<TextBox Text="{Binding ExamsTabExam.Name, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Center" Width="200" FontSize="16"/>
</StackPanel>
<StackPanel Grid.Row="1">
<Label Content="From Templates Tab:"/>
<Label FontWeight="Bold" Content="{Binding FromTemplatesTab.Name}"/>
</StackPanel>
I've started an MVVM project and now I'm stucking with correct DataBinding.
My project has:
A UserControl whit a ViewModel as DataContext like:
public partial class TestUserControl: UserControl
{
public TestUserControl()
{
this.DataContext = new TestUserControlViewModel();
}
}
ViewModel code is (BaseViewModel class contains PropertyChangedEventHandler):
public class TestUserControlViewModel : BaseViewModel
{
public KrankenkasseControlViewModel()
{}
public IEnumerable<DataItem> GetAllData
{
get
{
IGetTheData src= new DataRepository();
return src.GetData();
}
}
}
IGetTheData is the interface to DataContext:
public interface IGetTheData
{
IEnumerable<DataItem> GetData();
}
}
and finally the DataRepository code:
public class DataRepository : IGetTheData
{
private TestProjectDataContext dax = new TestProjectDataContext();
public IEnumerable<DataItem> GetData()
{
return (from d in this.dax.TestData
select new DataItem
{
ID = d.ID,
SomeOtherData = d.SomeOtherData
});
}
}
My UserControl has a few TextBoxes, but what's the best way to bind correctly?
Thanks for your help, regards.
EDIT: Binding the data against multiple textboxes
After reading your comment, I will elaborate my example for textboxes.
First important thing is that the ViewModel will model the things in the View, so that the View gets all information it needs in the structure it needs. That means, if you have multiple textboses in the View, you will need multiple string Properties in your ViewModel, one for each textbox.
In your XAML you could have something like
<TextBox Text="{Binding ID, Mode=TwoWay}" />
<TextBox Text="{Binding SomeOtherData, Mode=TwoWay}" />
and in your ViewModel
public class TestUserControlViewModel : BaseViewModel {
private string id;
private string someOtherData;
public TestUserControlViewModel() {
DataItem firstItem = new DataRepository().GetData().First();
this.ID = firstItem.ID;
this.SomeOtherData = firstItem.SomeOtherData;
}
public string ID {
get {
return this.id;
}
set {
if (this.id == value) return;
this.id = value;
this.OnPropertyChangedEvent("ID");
}
}
public string SomeOtherData {
get {
return this.someOtherData;
}
set {
if (this.someOtherData == value) return;
this.someOtherData = value;
this.OnPropertyChangedEvent("SomeOtherData");
}
}
}
Here I assume that in your BaseViewModel there is an OnPropertyChangedEvent method to fire the corresponding event. This tells the View that the property has changed and it must update itself.
Note the Mode=TwoWay in the XAML. This means, that it doesn't matter on which side the value changes, the other side will reflect the change immediately. So if the user changes a value in a TwoWay bound TextBox, then the corresponding ViewModel property will automatically change! And also vice versa: if you change the ViewModel property programmatically, the View will refresh.
If you want to show multiple textboxes for more than one data item, then you must introduce more Properties in the ViewModel and bind them accordingly. Maybe a ListBox with a flexible number of TextBoxes inside is a solution then, like #Haspemulator already answered.
Binding the data against a collection control
In the TestUserControl I guess you have a control (like a ListView) to show the list of loaded things. So bind that control against the list in the ViewModel with
<ListView ... ItemsSource="{Binding GetAllData}" ... />
First you must understand that Binding means not "read the data and then forget the ViewModel". Instead you bind the View to the ViewModel (and its Properties) as long as the View lasts. From this point of view, AllData is a much better name than GetAllData (thanks #Malcolm O'Hare).
Now in your code, every time the View reads the AllData property, a new DataRepository is created. Because of the Binding, that is not what you want, instead you want to have one instance of DataRepository for the whole lifetime of the View, which is used to read the initial data and can later be used to update the View, if the underlying database changes (maybe with an event).
To enable such a behavior you should change the type of the AllData property to an ObservableCollection, so that the View can automatically update the list if changes occur.
public class TestUserControlViewModel : BaseViewModel
private ObservableCollection<DataItem> allData;
public TestUserControlViewModel() {
IGetTheData src = new DataRepository();
this.allData = new ObservableCollection<DataItem>(src.GetData());
}
public ObservableCollection<DataItem> AllData {
get {
return this.allData;
}
}
public void AddDataItem(DataItem item) {
this.allData.Add(item);
}
}
Now if you call AddDataItem later, the ListView will update itself automatically.
Your Property Name is bad. You should call it AllData, not GetAllData.
Since you are returning a collection, you probably should be using some sort of list control (ListBox, ListView).
In that case you'd be doing
<ListBox ItemsSource="{Binding GetAllData}" />
Guten Abend. :) As it already mentioned, since you're returning the collection, it's better to use a ListBox. The comment about having ObservableCollection as a cache is also absolutely valid. I would add that if you need to have your data editable, you should use TextBox inside the ItemTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text={Binding SomeOtherData,Mode=TwoWay} />
</DataTemplate>
</ListBox.ItemTemplate>
In this case if user edits the text in the box, data will be updated in your data object, so that it could be saved in the database later.