I am desperately trying to implement MVVM and for some reason it is not working. I am using MVVM light on a Windows 8.1 Store App.
What am I doing wrong? I followed three tutorials by now and nothing seems to work..
I retrieve the Data from a Webservice and that part 100% works just fine. The ObservableCollection contains data.
The rest of my code looks like this:
ViewModelLocator:
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
// Create design time view services and models
SimpleIoc.Default.Register<IDesignTimeWeatherServiceLayer, DesignTimeWeatherServiceLayer>();
}
else
{
// Create run time view services and models
SimpleIoc.Default.Register<IWeatherServiceLayer, WeatherServiceLayer>();
}
SimpleIoc.Default.Register<WeatherViewModel>();
}
public WeatherViewModel Weather
{
get
{
return ServiceLocator.Current.GetInstance<WeatherViewModel>();
}
}
ViewModel:
public class WeatherViewModel : ViewModelBase
{
WeatherServiceLayer serviceLayer = new WeatherServiceLayer();
public async void GetAllWeatherData()
{
WeatherData = await serviceLayer.GetAllWeatherAsync();
}
private ObservableCollection<Weather> weatherData;
public ObservableCollection<Weather> WeatherData { get { return weatherData; } set { weatherData = value; RaisePropertyChanged("WeatherData"); } }
}
Code Behind:
public MainPage()
{
this.InitializeComponent();
WeatherViewModel vm = new WeatherViewModel();
vm.GetAllWeatherData();
}
View:
...
DataContext="{Binding Weather, Source={StaticResource Locator}}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<GridView ItemTemplate="{StaticResource WeatherItemTemplate}" ItemsSource="{Binding Weather.WeatherData, Source={StaticResource Locator}}"/>
</Grid>
DataTemplate:
<DataTemplate x:Key="WeatherItemTemplate">
<StackPanel>
<TextBlock Text="{Binding Temperature}" Height="60" Margin="15,0,15,0"/>
<TextBlock Text="{Binding WeekDay}" Margin="15,0,15,10"/>
</StackPanel>
</DataTemplate>
I'm not sure but there are a couple things that seem suspect to me. Initially you set your DataContext property. Are your sure your DataContext is what you're expecting after this assignment? You can set a breakpoint in your view's constructor after you call InitializeComponent to check.
Further in your ItemsSource binding you're saying "Weather.WeatherData" sourced by your locator. This seems redundant and maybe wrong. Maybe just try "WeatherData" and remove the Source specification. If your DataContext is a Weather, then that will be the object used for all bindings in that xaml file.
Related
I'm basically asking the same question as this person, but in the context of the newer x:Bind.
ViewModels' DataContext is defined like so
<Page.DataContext>
<vm:ChapterPageViewModel x:Name="ViewModel" />
</Page.DataContext>
So whenever I need to bind something I do it explicitely to the ViewModel like so
ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}"
However that doesn't work within templates
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}"> <-- this here is the culprit
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
Reading the documentation, I found that using Path should basically reset the context to the page, but this (x:Bind Path=ViewModel.PageResizeEvent didn't work either. I'm still getting Object reference not set to an instance of an object, which should mean that it doesn't see the method (but a null).
Image class:
public class Image {
public int page { get; set; }
public string url { get; set; }
public int width { get; set; }
public int heigth { get; set; }
}
And in the ChapterPageViewModel
private List<Image> _pageList;
public List<Image> pageList {
get { return _pageList; }
set { Set(ref _pageList, value); }
}
public override async Task OnNavigatedToAsync(object parameter, NavigationMode mode,
IDictionary<string, object> suspensionState)
{
Initialize();
await Task.CompletedTask;
}
private async void Initialize()
{
pageList = await ComicChapterGet.GetAsync(_chapterId);
}
public void PageResized(object sender, SizeChangedEventArgs e)
{
//resizing logic happens here
}
We have two problems here:
First, trying to directly bind an event to a event handler delegate
That will never work, simply put.
One way to handle an event on MVVM pattern is by using EventTrigger and ICommand.
It requires a class that implements ICommand. This post will help you if don't know how to do it. I'll call mine DelegateCommand.
Here's how I would refactor it in two steps:
1) Add a Command to the VM:
public class ChapterPageViewModel
{
public ChapterPageViewModel()
{
this.PageResizedCommand = new DelegateCommand(OnPageResized);
}
public DelegateCommand PageResizedCommand { get; }
private void OnPageResized()
{ }
}
2) Bind that Command to the SizeChanged event with EventTrigger and InvokeCommandAction.
<Page (...)
xmlns:i="using:Microsoft.Xaml.Interactivity"
xmlns:core="using:Microsoft.Xaml.Interactions.Core">
(...)
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" >
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{x:Bind ViewModel.PageResizedCommand }" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
</Page>
"But Gabriel", you say, "that didn't work!"
I know! And that's because of the second problem, which is trying to x:Bind a property that does not belong to the DataTemplate class
This one is closely related to this question, so I´ll borrow some info from there.
From MSDN, regarding DataTemplate and x:Bind
Inside a DataTemplate (whether used as an item template, a content
template, or a header template), the value of Path is not interpreted
in the context of the page, but in the context of the data object
being templated. So that its bindings can be validated (and efficient
code generated for them) at compile-time, a DataTemplate needs to
declare the type of its data object using x:DataType.
So, when you do <ScrollViewer SizeChanged="{x:Bind ViewModel.PageResized}">, you're actually searching for a property named ViewModel on the that models:Image class, which is the DataTemplate's x:DataType. And such a property does not exist on that class.
Here, I can see two options. Choose one of them:
Add that ViewModel as a property on the Image class, and fill it up on the VM.
public class Image {
(...)
public ChapterPageViewModel ViewModel { get; set; }
}
public class ChapterPageViewModel
{
(...)
private async void Initialize() {
pageList = await ComicChapterGet.GetAsync(_chapterId);
foreach(Image img in pageList)
img.ViewModel = this;
}
}
With only this, that previous code should work with no need to change anything else.
Drop that x:Bind and go back to good ol'Binding with ElementName.
<FlipView ItemsSource="{x:Bind ViewModel.pageList, Mode=OneWay}" x:Name="flipView">
<FlipView.ItemTemplate>
<DataTemplate x:DataType="models:Image">
<ScrollViewer>
<i:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="SizeChanged">
<core:InvokeCommandAction
Command="{Binding DataContext.PageResizedCommand
, ElementName=flipView}" />
</core:EventTriggerBehavior>
</i:Interaction.Behaviors>
<Image Source="{x:Bind url}"/>
</ScrollViewer>
</DataTemplate>
</FlipView.ItemTemplate>
</FlipView>
This one kind of defeat the purpose of your question, but it does work and it's easier to pull off then the previous one.
I am building a WPF application with mahapps, prism[modularity]. I have below HomeWindow.xaml code.
<Controls:MetroWindow x:Class="Project.Views.HomeWindow"
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:Controls="clr-namespace:MahApps.Metro.Controls;assembly=MahApps.Metro"
xmlns:local="clr-namespace:Project.Views"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True"
<!--The above code is for automatically binding of viewmodel into view-->
Height="700" Width="1200" Background="White">
<Grid>
<TabControl ItemsSource="{Binding TabCollection}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock>
<TextBlock Text="{Binding Name}"/>
</TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Label Content="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Controls:MetroWindow>
I have below structure in my HomeViewModel.cs under ViewModels directory.
public class HomeViewModel : BindableBase
{
private ObservableCollection<Item> _tabCollection;
public ObservableCollection<Item> TabCollection { get { return _tabCollection; } set { SetProperty(ref _tabCollection, value); } }
//Prism way of getting and setting data
}
public class Item
{
private string Name;
private string Content;
public Item(string name, string content)
{
Name = name;
Content = content;
}
}
below is how I add data into TabCollection property through HomeWindow.xaml.cs.
private HomeViewModel _model=new HomeViewModel();
public HomeWindow(EmployeeViewModel model)
{
InitializeComponent();
_model.UserViewModel = model;
LoadHomeData(_model.UserViewModel.EmpRole);
DataContext = this;
}
private void LoadHomeData(string Role)
{
if (string.Equals(Role, "Admin"))
{
_model.TabCollection= new ObservableCollection<Item>()
{
new Item("Test1", "1"),
new Item("Test2", "2"),
new Item("Test3", "3")
};
}
}
Now matter what, the tabs will not get displayed. Its a blank empty window. I have followed the example in the issue here and have went through few similar posts having same kind of approach. But none of them helped. Is this because of prism way of databinding or is there anything else am missing here? Hope to find some help on this..
Your problem is not connected to MahApps or Prism but to how WPF works in general. In your case Name and Content are private fields and should be public properties
public string Name { get; set; }
public string Content { get; set; }
private or field is not a valid binding source. You can find more as to what is a valid binding source under Binding Sources Overview but in your case, as far as CLR object goes:
You can bind to public properties, sub-properties, as well as indexers, of any common language runtime (CLR) object. The binding engine uses CLR reflection to get the values of the properties. Alternatively, objects that implement ICustomTypeDescriptor or have a registered TypeDescriptionProvider also work with the binding engine.
Another problem is that DataContext is set wrong. At the moment is set to HomeWindow and I think it should be set to instance of HomeViewModel which holds TabCollection property
DataContext = _model;
I have a longlistselector and in each row I have ToggleSwitch and I would like to call http request via my ApiService when ToggleSwitch is changed. I have ApiService class in ViewModel thanks to injection and in ViewModel I have ObservableCollection of Modules which have switches. I bind it with datatemplate and there is no problem with bind ToggleSwitch to bool property. But what should I do in setter of that property?
Model - Modul.cs
public int IsLock
{
get { return isLock; }
set {
Set(() => IsLock, ref isLock, value);
// What should I do here? How call ViewModel method?
}
}
ViewModel - ModuleListViewModel.cs
public ObservableCollection<Module> Modules { get; private set; }
// here I have apiService instance
// and here I could call apiService.Lock(module) and so on
View - part of DataTemplate
<toolkit:ToggleSwitch x:Name="LockSwitch"
IsChecked="{Binding IsLock, Mode=TwoWay}"/>
What's the right aproach for this? Maybe I could have ApiService class in each Modul class but I think that's very bad. I think ViewModel should somehow findout that Model was changed and it should call method.
I suggest using the ToggleSwitch's Command property -- that will get executed every time the user changes the toggle, and will allow you to bind to the parent data context. Use something like this in the XAML:
<ItemsControl x:Name="items" ItemsSource="{Binding Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<toolkit:ToggleSwitch x:Name="LockSwitch"
Command="{Binding ElementName=items,Path=DataContext.LockToggleCommand}"
CommandParameter="{Binding}"
IsChecked="{Binding IsLock, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Then just add the "LockToggleCommand" to your main view model, and call the service, eg:
public ObservableCollection<Module> Modules { get; private set; }
public ICommand LockToggleCommand { get; private set; }
public ViewModel()
{
LockToggleCommand = new DelegateCommand<Module>(module => {
apiService.Lock(module);
});
}
Here "DelegateCommand" is just the usual implementation of ICommand -- I am sure that MVVM Light has its own standard implementation.
Edit
I thought that ToggleSwitch supported Command, but since it doesn't, you can take a similar approach using an EventTrigger (if you are willing to add the System.Windows.Interactivity and Microsoft.Expression.Interactions DLLs to your project):
<ItemsControl x:Name="items" ItemsSource="{Binding Modules}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<toolkit:ToggleSwitch x:Name="LockSwitch"
IsChecked="{Binding IsLock, Mode=TwoWay}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Toggled">
<ei:CallMethodAction TargetObject="{Binding ElementName=items,Path=DataContext}"
MethodName="OnToggled"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now add the "OnToggled" method to the main view model -- use the "sender" parameter to get the current item, something like this:
public void OnToggled(object sender, RoutedEventArgs e)
{
var toggleSwitch = (ToggleSwitch)sender;
var module = (Module)toggleSwitch.DataContext;
apiService.Lock(module);
}
I submitted this accidently, I fixed it myself and wasn't going to submit after writing out the question. But have learnt from the comments, thanks!
I am trying to create a simple todo app in win8 and eventually want to hock it into ToDoIst API.
I have created a simple task class to try and get my head around the databinding however I just can not get it to do what I want to do. I have used listboxes and other basic form elements.
task.cs
class task
{
private string content;
private bool complete;
public string Content
{
get {return content;}
set { content = value; }
}
public bool Complete
{
get { return complete; }
set { complete = value; }
}
public task(string content)
{
Content = content;
Complete = false;
}
}
MainPage.xaml
And at the moment my XAML looks like this.
<GridView HorizontalAlignment="Left" Margin="482,190,0,0" VerticalAlignment="Top" Width="400" Height="500">
<ListView x:Name="LVtasks" HorizontalAlignment="Left" Height="500" VerticalAlignment="Top" Width="400" ItemsSource="{Binding}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Content}"/>
<RadioButton/>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</GridView>
I have put in some dummy data, 4 elements and when I run it, it comes up with 4 boxes with radio buttons however no text (there is space for the text) I am not sure how I would bind the bool?
I can not see what I am doing wrong. If anyone could help and point me in the right direction, I have searched a fair amount of tutorials and just can not figure it out.
Your code looks a little strange, maybe this is what you want:
<ListView x:Name="LVtasks" HorizontalAlignment="Left" Height="500" VerticalAlignment="Top" Width="400" ItemsSource="{Binding ToDoItems}">
<ListView.ItemTemplate>
<DataTemplate>
<RadioButton GroupName="ToDos" Content="{Binding Content}" IsChecked="{Binding IsComplete}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
Do you really want radiobuttons? I think you want Checkboxes, the difference is that when you use radiobuttons only one in a group can be 'checked'
I used this code behind to have a datacontext:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ToDoItems = new ObservableCollection<TodoItem>(new List<TodoItem>
{
new TodoItem("Content1"),
new TodoItem("Content2")
});
this.DataContext = this;
}
public ObservableCollection<TodoItem> ToDoItems { get; set; }
}
I changed the name of task to ToDoItem Task is already a class in the framework and might cause confusion.
For the RadioButton IsChecked is the property to bind to a bool property:
<RadioButton IsChecked="{Binding Path=Complete}"/>
Your text is most likely not showing up because you haven't set up any change notifications and the binding is happening before you set the Content values. Using the INotifyPropertyChanged interface is the most common and usually the easiest way to do this.
Like John already mentioned you should really let the window containing your listview implement the INotifyPropertyChanged interface. And to set the data context of your window like Johan said. It is important that you call the propertychanged method in each setter of a property. It is also useful to use an ObservableCollection as ItemSource of your listview. Try to create an instance of ObservableCollection, create a property for it calling propertychanged method in its setter an set rhe ItemSource of your listview to the property. Do not forget to also call propertychanged whenever you add or remove items from the collection
Hello I have a application design problem and I home you can help me solve it....
This is my first application in silverlight and the first application using mvvm design pattern and I am not sure I am applying mvvm how I am supposed to..
The application is a dynamic application and at runtime I can add/remove usercontrols...
So I have a MainWindowView that has behind a MainWindowModel.
The MainWindowModel has a list of Workspaces witch are in fact WorkspaceModel classes...
I have multiple UserControls and everyone off them has his own view model witch inherits WorkspaceModel.
The Workspaces property is binded to a container in MainWindowView so adding to the Workspaces list a new UserControlModel will automatically add that control to the view.
Now where is my problem... I want to make this dynamically added usercontrols to interact. Lets say one user control is a tree and one is a grid... I want a method to say that Itemsource property of Grid UserControl Model (WorkspaceModel) to be binded to SelectedNode.Nodes Property from the Tree Usercontrol Model (WorkspaceModel).
The MainWindowModel has a property name BindingEntries witch has a list of BindingEntry...
BindingEntry stores the source property and the destination property of the binding like my workspacemodel_1.SelectedNode.Nodes -> workspacemodel_2.ItemSource...
Or as a variation the MainWindowView has a property ViewStateModel. This ViewStateModel class has dynamic created properties - "injected" with property type descriptors/reflections etc... So the user can define at run time the displayed usercontrols (by modifying the Workspaces list) and can define a view model (the ViewStameModel) and the binding is between workspacemodel properties and this ViewStateModel properties...
So I actually want to bind 2 view models one to another... How to do that?
Create an observer pattern?
Is the design until now totally wrong?
I hope it makes sense.....
I will try to add some sample code...the project is quite big I will try co put only the part I have mentioned in the problem desciption... I hope I will not miss any pice of code
First of all
public class MainWindowModel : ModelBase
{
private ObservableCollection<WorkspaceModel> _workspaces;
private ModelBase _userViewModel;
public MainWindowModel()
{
base.DisplayName = "MainWindowModel";
ShowTreeView(1);
ShowTreeView(2);
ShowGridView(3);
ShowGridView(4);
UserViewModel = new ViewModel(); //this is the ViewStateModel
}
void ShowTreeView(int id)
{
WorkspaceModel workspace = ControlFactory.CreateModel("TreeControlModel", id);
this.Workspaces.Add(workspace);
OnPropertyChanged("Workspaces");
SelectedWorkspace = workspace;
}
void ShowGridView(int id)
{
WorkspaceModel workspace = ControlFactory.CreateModel("GridControlModel", id);
this.Workspaces.Add(workspace);
OnPropertyChanged("Workspaces");
SelectedWorkspace = workspace;
}
public ObservableCollection<WorkspaceModel> Workspaces
{
get
{
if (_workspaces == null)
{
_workspaces = new ObservableCollection<WorkspaceModel>();
}
return _workspaces;
}
}
public ModelBase UserViewModel
{
get
{
return _userViewModel;
}
set
{
if (_userViewModel == value)
{
return;
}
_userViewModel = value;
OnPropertyChanged("UserViewModel");
}
}
}
snippets from MainappView
<DataTemplate x:Key="WorkspaceItemTemplate">
<Grid >
//workaround to use Type as in WPF
<Detail:DetailsViewSelector Content="{Binding}" TemplateType="{Binding}" >
<Detail:DetailsViewSelector.Resources>
<DataTemplate x:Key="TreeControlModel" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<TreeControl:TreeControlView />
</DataTemplate>
<DataTemplate x:Key="GridControlModel" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<GridControl:GridControlView />
</DataTemplate>
<DataTemplate x:Key="EmptyTemplate">
</DataTemplate>
</Detail:DetailsViewSelector.Resources>
</Detail:DetailsViewSelector>
</Grid>
</DataTemplate>
<DataTemplate x:Key="WorkspacesTemplate">
<ItemsControl IsTabStop="False" ItemsSource="{Binding}" ItemTemplate="{StaticResource WorkspaceItemTemplate}" Margin="6,2"/>
</DataTemplate>
</UserControl.Resources>
<toolkit:HeaderedContentControl Grid.Column="2" HorizontalAlignment="Stretch" Name="hccWorkspaces" VerticalAlignment="Top" Header="Workspaces" Content="{Binding Source={StaticResource vm}, Path=Workspaces}" ContentTemplate="{StaticResource WorkspacesTemplate}"/>
public class ControlFactory
{
public static WorkspaceModel CreateModel(string type, int id)
{
switch (type)
{
case "TreeControlModel": return new TreeControlModel() { Id=id}; break;
case "GridControlModel": return new GridControlModel() { Id = id }; break;
}
return null;
}
}
public class GridControlModel : WorkspaceModel
{
#region Fields
ObservableCollection<TreeItem> _items;
TreeItem _selectedItem;
#endregion // Fields
#region Constructor
public GridControlModel()
{
base.DisplayName = "GridControlModel";
}
#endregion // Constructor
#region Public Interface
public ObservableCollection<TreeItem> Items
{
get
{
return _items;
}
set
{
if (_items == value)
return;
_items = value;
OnPropertyChanged("Items");
}
}
public TreeItem SelectedItem
{
get
{
return _selectedItem;
}
set
{
if (_selectedItem.Equals(value))
{
return;
}
_selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
#endregion // Public Interface
#region Base Class Overrides
protected override void OnDispose()
{
this.OnDispose();
}
#endregion // Base Class Overrides
}
<Grid x:Name="LayoutRoot" Background="White">
<sdk:DataGrid Grid.Column="2" Grid.Row="1" HorizontalAlignment="Stretch" Name="dataGrid1" VerticalAlignment="Stretch" ItemsSource="{Binding Path=SelectedTreeNode.Children}" IsEnabled="{Binding}" AutoGenerateColumns="False">
<sdk:DataGrid.Columns>
<sdk:DataGridTextColumn CanUserReorder="True" CanUserResize="True" CanUserSort="True" Header="Id" Width="Auto" Binding="{Binding Id}" />
<sdk:DataGridTextColumn CanUserReorder="True" CanUserResize="True" CanUserSort="True" Header="Name" Width="Auto" Binding="{Binding Name}"/>
</sdk:DataGrid.Columns>
</sdk:DataGrid>
</Grid>
</UserControl>
For communicating between your view models I would suggest taking a look at the Messenger implementation in MVVM Light as a simple solution.
Alternatively, the Mediator Pattern as described here might be interesting: http://marlongrech.wordpress.com/2008/03/20/more-than-just-mvc-for-wpf/