Given the following classes:
public class Neighborhood
{
public IEnumerable<House> Houses { get; set; }
}
public class House
{
public Address Address { get; set; }
public IEnumerable<Room> Rooms { get; set; }
}
public class Room
{
public IEnumerable<Furniture> Furniture { get; set; }
}
I want views that look something like this:
<!-- Want DataContext to be a NeighborhoodViewModel, not a Neighborhood -->
<NeighborhoodView>
<ListBox ItemsSource={Binding Houses}/>
<Button Content="Add House"/>
<Button Content="Remove House"/>
</NeighborhoodView>
<!-- Want DataContext to be a HouseViewModel, not a House-->
<HouseView>
<TextBox Text={Binding Address}/>
<ListBox ItemsSource={Binding Rooms}/>
<Button Content="Add Room"/>
<Button Content="Remove Room"/>
</HouseView>
<!-- Want DataContext to be a RoomViewModel, not a Room -->
<RoomView>
<ListBox ItemsSource={Binding Furniture}/>
<Button Content="Add Furniture"/>
<Button Content="Remove Furniture"/>
</RoomView>
However, I don't want NeighborhoodViewModel to contain HouseViewModels. Rather, it should be like:
public class NeighborhoodViewModel
{
public IEnumerable<Room> Rooms { get; }
public ICommand AddHouseCommand { get; }
public ICommand RemoveHouseCommand { get; }
}
How can I declare bindings to models in XAML, but have the bindings be transformed into viewmodels?
There are two general ways you can create this type of effect. The first way is to create a static view and put a DataContext behind each view. This is not MVVM where the views are generated by the ViewModel but this will get your bindings to work. This is an example of view.
<Grid>
<HouseView x:Name="myHouse">
</HouseView>
</Grid>
In the code behind you can get access to your HouseView and set the data context
public MainWindow()
{
myHouse.DataContext = new MyHouseViewModel();
}
This will get your bindings to work for each one of these controls.
I will say that this is not the best practice of WPF and it is certainly not a way to develop large projects. However this will do for quick and dirty coding in my opinion. You can find out more on how to do proper MVVM style of coding here.
Related
I need some suggestions, help to solve any problems. I have to create a view with a dynamic count of ContentPages. I created two ViewModels, one with a logic and a stop watch and a second one to control the dynamic pages. It looks like this:
CircleViewModel:
public class CircleViewModel: ViewModelBase {
public ObservableCollection<Circle> Circles { get; set; }
private string _timeText;
public string TimeText {
get => _timeText;
set {
_timeText = value;
OnPropertyChanged();
}
// Some ICommands and methods
public async Task StartStopWatch() { ... }
}
MultiPageViewModel:
public class MultiPageViewModel : ViewModelBase {
public ObservableCollection<CircleViewModel> Pages { get; set; }
private CircleViewModel _currentPage;
public CircleViewModel CurrentPage {
get => _currentPage;
set {
_currentPage = value;
OnPropertyChanged();
}
// Some ICommands and methods
}
MultiPageView.xaml:
<ContentPage ...
BindingContext="{Binding MultiPageViewModel, Source={StaticResource Locator}}">
<Grid>
<ListView ItemsSource="{Binding CurrentPage.Circles}"/>
<Label Text="{Binding CurrentPage.Title}"/>
<Button Text="{Binding CurrentPage.TimeText}" Command="{Binding CurrentPage.StartWatchCommand}"/>
<userControls:VerticalTabView ItemsSource="{Binding MultiPageViewModel.Pages, Source={StaticResoruce Locator}}"
MoreButtonCommand="{Binding MultiPageViewModel.MoreButtonCommand, Source={StaticResource Locator}}"/>
</Grid>
</ContentPage>
VerticalTabView is a user control to change the current page.
I did'nt like the usability, so I changed the ContentPage to CarouselPage. With UWP it works fine, with Android the scrolling starts to stutter, in the output of Visual Studio appears "The application may be doing too much work on its main thread." and crashes with an OutOfMemory Exception. The CarouselPage does not work with iOS currently.
How can I improve this? I am grateful for any advice. Thank you.
I am trying to implement a create mask, on which a user can create a new technology with different versions (e.g: .NET with versions 4.5.2 and 4.6 etc.). For that I want the user to be able to dynamically add text boxes for additional versions.
A requirement for this is to use the MVVM Pattern, which i'm fairly new to. I created the following classes for this:
Entitiy Classes (using Entity Framework)
public class Tech : EntityBase
{
// Properties
public string TechName { get; set; }
// Navigation Properties
public virtual ICollection<TechVersion> TechVersions{ get; set; }
}
public class TechVersion : EntityBase
{
// Properties
public string VersionNumber { get; set; }
// Foreign Keys
//public int TechId{ get; set; }
// Navigation Properties
public virtual Tech Tech{ get; set; }
}
Technology View Model
public class TechnologyUpdateViewModel : ObservableObject
{
private string _technologyName;
public string TechnologyName
{
get
{
return _technologyName;
}
set
{
if (_technologyName != value)
{
_technologyName = value;
OnPropertyChanged("TechnologyName");
}
}
}
private ICommand _add;
public ICommand Add
{
get
{
if (_add == null)
{
_add = new RelayCommand(e => CreateTechnology());
}
return _add;
}
set
{
if (_add != value)
{
_add = value;
OnPropertyChanged("Add");
}
}
}
private void CreateTechnology()
{
// Add new Technology from ViewModel Properties
}
}
I am however having trouble with the XAML Code. Binding the Technology Name and adding a new Technology with just the name works fine (Textbox + Button).
But how do I get the dynamically created Textboxes and bind them to the ViewModel?
I've tried the approach I found here
For this I added public ObservableCollection<string> Versions { get; set; }
But most solutions try to create controls from an already filled list. I am trying to create text boxes and bind them to an empty list.
TechnologyAdd.xaml
<UserControl x:Class="Presentation.Views.TechnologyAddOrEdit"
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:local="clr-namespace:Presentation.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<StackPanel>
<StackPanel MaxWidth="250">
<DockPanel>
<TextBlock>Name: </TextBlock>
<TextBox Text="{Binding TechnologyName}" Margin="10,0,0,0" />
</DockPanel>
<!-- Approach from the Link -->
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<ItemsControl ItemsSource="{Binding Versions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Button Content="Save" Command="{Binding Add}" />
</StackPanel>
</StackPanel>
Maybe the whole MVVM thing confuses me too much but I can't seem to find a solution for this.
I would be happy about any information on how to approach this correctly.
In my application, I need to bind a checkbox list to an observable collection. I have seen many examples but I could not find a proper implementation for this and thats why I am posting this question.
The View:
<Grid Name="GrdMain" Background="White">
<ListView Name="lstConditions" VerticalAlignment="Top" Height="150"
ItemsSource="{Binding ConditionsModels}" Margin="0,25,0,0" BorderBrush="Transparent" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Content="{Binding Path=condition}" Margin="8" Style="{StaticResource CheckBoxDefault}"
IsChecked="{Binding hasCondition,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</grid>
The model:
public class ConditionsModel
{
public int profileId { get; set; }
public string condition { get; set; }
public bool hasCondition { get; set; }
}
The View Model:
public class ConditionsViewModel : INotifyPropertyChanged
{
private ConditionsModel _conditionsModel;
private ObservableCollection<ConditionsModel> _conditionsModels;
public ConditionsModel ConditionsModel
{
get
{
return _conditionsModel;
}
set
{
_conditionsModel = value;
RaisePropertyChanged("ConditionsModel");
}
}
public ObservableCollection<ConditionsModel> ConditionsModels
{
get
{
return _conditionsModels;
}
set
{
_conditionsModels = value;
RaisePropertyChanged("ConditionsModels");
}
}
public ConditionsViewModel(int profileId)
{
ConditionsModel = new ConditionsModel();
ConditionsModels = new ObservableCollection<ConditionsModel>();
ConditionsModels.CollectionChanged += ConditionsModels_CollectionChanged;
GetConditions(profileId);
}
void ConditionsModels_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("ConditionsModels");
}
private void GetConditions(int profileId)
{
HealthAssessmentRepository _rep = new HealthAssessmentRepository();
_conditionsModels = _rep.GetConditions(profileId);
}
}
Is this a correct implementation? I need to update the model when the user checks or unchecks the checkbox. But its not raising the propery changed event when the check box is checked or unchecked.Should I implement the INotifyPropertyChanged interface on the model as well?
I have seen many examples, but all of them has different approaches to this and I am confused. Please show the correct implementation of this?
Thanks
I think you have missed the DataType property within DataTemplate. Just refer this
<DataTemplate DataType="{x:Type sampleApp:ConditionsModel}">
Here sampleApp in the namespace reference created within tag. And ConditionsModel is your model class.
You need to implement INotifyPropertyChanged for class ConditionsModel and raise PropertyChangedEvent for the property you want to observe/synchronize, because it is ViewModel as well.
For class ConditionsViewModel, it's the ViewModel of whole ListView, for ConditionsModel, it's the ViewModel of every line. ViewModel can be overlaid. If ConditionsModel is the domain model, my suggestion is that add a new ItemViewModel, because they belong to different layers. It's always better to distinguish the different layers properly.
I have a WPF forms and a class Users (content attributes Id, Login and Name), in my class of this forms, I had get a Users object for put this information in the form with DataContext and Binding
I can put this Users object to my Window.DataContext (this.DataContext = usersObject;) with code behind, but I think if I can make this with XAML, maybe is better
I have set a attribute in my class UserForm (public Users usersObject {get; set;})
My form UserForm : Window
<Window DataContext="{What need I put here?">
<Grid>
<TextBlock Text="Id:"/>
<TextBox Name="Id" Text="{Binding Path=Id}"/>
<TextBlock Text="Login:"/>
<TextBox Name="Login" Text="{Binding Path=Login}"/>
<TextBlock Text="Name:"/>
<TextBox Name="Name" Text="{Binding Path=Name}"/>
</Grid>
</Window>
UserForm.xaml.cs
public class UserForm : Window
{
public Users userObject { get; set; }
public UserForm(Users user)
{
InitializeComponent();
this.userObject = user;
}
}
My class Users
public class Users
{
public int Id { get; set; }
public string Login { get; set; }
public string Name { get; set; }
}
How to I do for set userObject in the itself Window.DataContext for TextBox's can put it values?
Some time back I had written an article on different options of binding XAML control to code behind property. This might help you.
http://codingseason.blogspot.in/2013/05/data-binding-xaml-control-to-property.html
Remove the DataContext binding since you are not explicitly doing MVVM pattern. No point of doing the binding.
In your Window.xaml
<Window>
<Grid>
<TextBlock Text="Id:"/>
<TextBox Name="Id" Text="{Binding Path=Id}"/>
<TextBlock Text="Login:"/>
<TextBox Name="Login" Text="{Binding Path=Login}"/>
<TextBlock Text="Name:"/>
<TextBox Name="Name" Text="{Binding Path=Name}"/>
</Grid>
Add this to your Behind the code Window.xaml.cs
public class UserForm : Window
{
public Users userObject { get; set; }
public UserForm(Users user)
{
InitializeComponent();
this.DataContext = user;
}
}
If you would do it in MVVM you would have done something like this
ViewModel.cs
public class UserViewModel
{
private Users _model;
public UserViewModel(Users model)
{
_model = model;
}
public int Id { get { return _model.Id; } }
public string Login { get { return _model.Login; } set { _model.Login; } }
public string Name { get { return _model.Name; } set { _model.Name; } }
}
Now the ViewModel can be customize depending on what you need, you can expose the Model, inject it to the constructor or just set the property value if it is exposed. Don't forget to implement INotifyPropertyChanged if you want to propagate any values in the ViewModel back to the user interface.
View.xaml.cs
public class UserForm : Window
{
public UserForm(Users user)
{
InitializeComponent();
this.DataContext = new UserViewModel(user);
}
View.xaml
You have two choices, you can explicitly set the DataContext just like what I did behind the code or you can create a public property that returns the UserViewModel. It's just the same
Model.cs
public class Users { //Whatever properties you need }
Now this is a very simplistic example of MVVM pattern. Once you know the basics, you can then integrate some helpful frameworks that implements MVVM for you like Caliburn Micro and MVVM Toolkit
Create an object of UserForm and assigning data context is pretty simple.
UserForm userFormView = new UserForm ();
Users dataContextObject = new Users();
userFormView.DataContext = dataContextObject;
userFormView.Show() //you can also use showdialog. Thats up to you
Its better you remove the following code:
public Users userObject { get; set; }
public UserForm(Users user)
{
InitializeComponent();
this.userObject = user;
}
Its better to separate the view and the viewmodel. Have a look at MVVM and it will make more sense
This is one of the way to assign DataContext directly from xaml.
Example App.xaml
<Application.Resources>
<local:Users x:Key="Users"/>
</Application.Resources>
Example UserForm.xaml
<Window DataContext="{StaticResource Users}">
<Grid>
<TextBlock Text="Id:"/>
<TextBox Name="Id" Text="{Binding Path=Id}"/>
<TextBlock Text="Login:"/>
<TextBox Name="Login" Text="{Binding Path=Login}"/>
<TextBlock Text="Name:"/>
<TextBox Name="Name" Text="{Binding Path=Name}"/>
</Grid>
</Window>
Maybe you can define a DependencyProperty to wrap the access to the userObject,
then bind it to the DataContext
I have a class MyDataCollection that contains MyMetaData and MyData. In my application i have two usercontrolls that display input fields to the user. One for the MyMetaData and the other for MyData. Both usercontrols are included in the MainPage.
My Question is: How should i get the data from the usercontrols then the user klicks on the save-button (located on the mainpage)?
Update
I have changed my code accordingly to blindmeis post but now the MetaDataView is not shown:
<UserControl.Resources>
<DataTemplate x:Key="MetaDataTemplate">
<view:MetaDataView/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ContentPresenter Content="{Binding MetaDataTemplate}"/>
</Grid>
why not doing mvvm the easy way?(viewmodel first). you say you have a mainpage - this means you also have a mainpageviewmodel. your mainpageviewmodel handles at least the save command. now you want to show MyMetaData and MyData on your mainpage. so the easy way would be to create one MyMetaData instance and one MyData instance in your mainviewmodel.
public class MainPageViewmodel
{
public ICommand SaveCommand { get; set; }
public MyDataViewmodel MyData { get; set; }
public MyMetaDataViewmodel MyMetaData { get; set; }
public MainPageViewmodel()
{
this.MyData = new MyDataViewmodel();
this.MyMetaData = new MyMetaDataViewmodel();
}
}
public class MyDataViewmodel
{}
public class MyMetaDataViewmodel
{}
your mainpage just need 2 datatemplates and 2 contentpresenter.
//resources
<DataTemplate DataType="{x:Type Local:MyDataViewmodel}">
<view:MyDataUserControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type Local:MyMetaDataViewmodel}">
<view:MyMetaDataUserControl/>
</DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ContentPresenter Content="{Binding MyData}" Grid.Column="0"/>
<ContentPresenter Content="{Binding MyMetaData}" Grid.Column="1"/>
<Button Content="Save" Command="{Binding SaveCommand}" Grid.Column="2"/>
</Grid>
because your mainpageviewmodel has both "child" viewmodel, you have all information you want on your savecommand.
if you have another scenario pls update your question, maybe post some code.
EDIT: i have no silverlight so that just a suggestion: maybe rachel can give you a better answer.
<Grid>
<ContentPresenter Content="{Binding MyMetaData}" ContentTemplate="{StaticResource MetaDataTemplate}"/>
</Grid>
if silverlight cant handle datatemplates with datatype you could just put the usercontrol there directly.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<view:MyDataUserControl DataContext="{Binding MyData}" Grid.Column="0"/>
<view:MyMetaDataUserControl DataContext="{Binding MyMetaData}" Grid.Column="1"/>
<Button Content="Save" Command="{Binding SaveCommand}" Grid.Column="2"/>
</Grid>
Since you tagged this question as MVVM, your ViewModels should contain both your SaveCommand and all the data needed to perform the actual save
Your MainViewModel should contain MyMetaData and MyData properties (which are bound to their respective UserControls), and each of those objects should contain properties for any data needed in the UserControl. For example, if your UserControl had a TextBox for Name, then your data object should have a property for the Name that the TextBox binds to.
If the Save button is located in one of those UserControls then the respective ViewModel should have a SaveCommand that gets executed when the Button is clicked. All the data needed for the Save is also located in that ViewModel, so you're good to go.
If your MainViewModel is in charge of saving the data, then it should be able to hook into your sub ViewModel's SaveCommand and attach it's own method, such as
this.MyData.SaveCommand = this.SaveCommand();
and all the data needed for the save can be found in this.MyData
If the SaveButton is located in your MainView, and not in one of the UserControls, then the SaveCommand should be part of MainViewModel, and all the data needed for the save can be found in this.MyData or This.MyMetaData.
Remember, with MVVM your ViewModels are your application. The View is just a pretty interface that allows users to interact with your ViewModels.
You should use Two-way bindings to automatically update the value in your controller. Take a look at this article.
Here's an example:
<TextBox Text="{Binding MyMetaData, Mode=TwoWay }" />
<TextBox Text="{Binding MyData, Mode=TwoWay }" />
I'll give you a little sample, how you can use the MVVM Light Messenger for ViewModel-to-ViewModel communication. Say you have an MyDataCollection class:
public class MyDataCollection
{
public int MyData;
public string MyMetaData;
}
On your MainViewModel you have a RelayCommand (from MVVM light toolkit) binded by your View's SaveButton. When the Connad is executed, you will have to send a Message with a callback action to request data from the subcriber. The callback takes MyDataCollection as parameter:
public class MainViewModel : ViewModelBase
{
public RelayCommand SaveCommand { get; private set; }
//Ctor
public MainViewModel()
{
SaveCommand = new RelayCommand(
() =>
Messenger.Default.Send<NotificationMessageAction<MyDataCollection>>(
new NotificationMessageAction<MyDataCollection>("SaveData", SaveCallback)));
}
private void SaveCallback(MyDataCollection dataCollection)
{
// process your dataCollection...
}
}
The UserControlViewModel has properties the InputTextBoxes are binded too. It just has to register to the message and call the callback with data properties:
public class UserControlViewModel : ViewModelBase
{
//Properties
public string UserControlMetaData { get; set; }
public int UserControlData { get; set; }
//Ctor
public UserControlViewModel()
{
Messenger.Default.Register<NotificationMessageAction<MyDataCollection>>(this, MessageReceived);
}
// private Method to handle all kinds of messages.
private void MessageReceived(MessageBase msg)
{
if(msg is NotificationMessageAction<MyDataCollection>)
{
var actionMsg = msg as NotificationMessageAction<MyDataCollection>;
if(actionMsg.Notification == "SaveData") // Is this the Message, we are looking for?
{
// here the MainViewModels callback is called.
actionMsg.Execute(new MyDataCollection() {MyData = UserControlData, MyMetaData = UserControlMetaData});
}
}
}
}
You will have to use messengers or you will have to set the properties over the ViewModelLocator
Messenger example of how I use it to set the UI language
ViewModel A, I register a listener here with the "SetLanguage" token:
Messenger.Default.Register<string>(this, "SetLanguage", false, input =>
{
SetLanguage(input);
});
ViewModel B, here I send the message with the "SetLanguage" token:
Messenger.Default.Send("en-EN", "SetLanguage");
ViewModelLocator example in ViewModel A, I access data in ViewModel B over the locator:
short value = 12;
var myFilteredDataList = ViewModelLocator.ViewModelBStatic.MyDataList.Any(m => m.code == value);
I have two solutions now:
View:
<ContentPresenter Content="{Binding MyMetaDataView}" />
ViewModel:
public MetaDataViewModel MyMetaDataViewModel { get; set; }
public MetaDataView MyMetaDataView { get; set; }
public MainViewModel()
{
MyMetaDataViewModel = new MetaDataViewModel();
MyMetaDataView = new MetaDataView();
MyMetaDataView.DataContext = MyMetaDataViewModel;
}
or ----
View:
<UserControl.Resources>
<DataTemplate x:Key="MetaDataViewTemplate">
<view:MetaDataView />
</DataTemplate>
</UserControl.Resources>
...
<ContentPresenter Content="{Binding MyMetaDataViewModel}" ContentTemplate="{StaticResource MetaDataViewTemplate}"/>
ViewModel:
public MetaDataViewModel MyMetaDataViewModel { get; set; }
public MainViewModel()
{
MyMetaDataViewModel = new MetaDataViewModel();
}