Using mvvmCross, .net (5.0), visual studio 19
View model:
using System.ComponentModel;
using System.Threading.Tasks;
using MvvmCross.ViewModels;
using MvxR_M_S.Core.API;
using MvxR_M_S.Core.Models;
namespace MvxR_M_S.Core.ViewModels
{
public class ArticlePresentationViewModel : MvxViewModel
{
public ArticlePresentationViewModel()
{
}
public override async void ViewAppeared()
{
base.ViewAppeared();
await LoadArticles(new ArticleEndpoint(new APIHelper()));
}
public async Task LoadArticles(ArticleEndpoint articleEndpoint)
{
var articleList = await articleEndpoint.GetAll();
Articles = new BindingList<ArticleModel>(articleList);
}
private BindingList<ArticleModel> _articles;
public BindingList<ArticleModel> Articles
{
get { return _articles; }
set
{
_articles = value;
SetProperty(ref _articles, value);
}
}
}
}
Model:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MvxR_M_S.Core.Models
{
public class ArticleModel
{
public string ArticleName { get; set; }
}
}
View:
<views:MvxWpfView
xmlns:views="clr-namespace:MvvmCross.Platforms.Wpf.Views;assembly=MvvmCross.Platforms.Wpf"
xmlns:mvx="clr-namespace:MvvmCross.Platforms.Wpf.Binding;assembly=MvvmCross.Platforms.Wpf"
x:Class="MvxR_M_S.Wpf.Views.ArticlePresentationView"
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"
xmlns:models="clr-namespace:MvxR_M_S.Core.Models;assembly=MvxR_M_S.Core"
xmlns:viewModels="clr-namespace:MvxR_M_S.Core.ViewModels;assembly=MvxR_M_S.Core"
mc:Ignorable="d" FontSize="20" Background="Wheat"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="20" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="20" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1" HorizontalAlignment="Center" Text="Article list" />
<ListBox x:Name="Articles" ItemsSource="{Binding Articles}" Grid.Column="2" Grid.Row="2" MinHeight="300" MinWidth="300" >
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ArticleName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</views:MvxWpfView>
The problem is at the listbox, where it references ItemsSource right, also i did debug at the Articles property inside ViewModel, it holds right information, but nothing is presented on screen when started up.
So i guess this is binding issue.
Remove _articles = value; in Articles setter. SetProperty doesn't do anything if the field already is set
public BindingList<ArticleModel> Articles
{
get { return _articles; }
set
{
SetProperty(ref _articles, value);
}
}
Since you are retrieving the articles in a separate thread, I think you have to do something like this:
Dispatcher.BeginInvoke(new Action(() => Articles = new BindingList<ArticleModel>(articleList);));
I am not exactly sure if it's the right way but Dispatcher.BeginInvoke seems needed from what I found
Related
When the View is initialized, their standard value "VM", definied in the ViewModel, is updated trough the Model and updated in the View. However, when the ICommand NavigationCommand is triggered, the code in the OnNavigationCommand method executes, and even the OnPropertyChanged (INotifyPropertyChanged) method is called in the Model. However, the textboxes in the UI still remains the same value: "VM". I have tried a lot, but can't seem to find the problem. Hope you can help.
View
<vw:View xmlns:cc="clr-namespace:HMI.CustomControl" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Class="HMI.ZoneFView"
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:vw="http://inosoft.com/visiwin7"
xmlns:main="clr-namespace:HMI.Views.MainRegion"
xmlns:local="clr-namespace:HMI" xmlns:dialogregion="clr-namespace:HMI.Views.DialogRegion"
mc:Ignorable="d"
DataContext="{vw:AdapterBinding ViewModel}">
<Viewbox>
<Grid x:Name="LayoutRoot" Width="140" Height="220.5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3"/>
<ColumnDefinition Width="5*"/>
<ColumnDefinition Width="3"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="5" />
<RowDefinition Height="5*" />
<RowDefinition Height="5" />
</Grid.RowDefinitions>
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding theModel.VisibilityFFUView}" Margin="57,16,7,62" />
<TextBox Grid.Row="1" Grid.Column="1" Text="{Binding theModel.VisibilityFFUView}" Margin="57,48,7,30" />
</Grid>
</Viewbox>
</vw:View>
View.cs
using HMI.Views.MainRegion;
using VisiWin.ApplicationFramework;
namespace HMI
{
[ExportView("ZoneFView")]
public partial class ZoneFView : VisiWin.Controls.View
{
public ZoneFView()
{
this.InitializeComponent();
}
}
}
ViewModel
using System;
using System.ComponentModel.Composition;
using System.Windows.Input;
using VisiWin.ApplicationFramework;
using VisiWin.Commands;
namespace HMI.Views.MainRegion
{
[ExportAdapter("ViewModel")]
[PartCreationPolicy(CreationPolicy.NonShared)]
class ViewModel : AdapterBase
{
#region Constructor
public Model Model { get; set; }
public ViewModel()
{
// If the VisiWin system is not in the runtime, the VW7 functionalities cannot be accessed
if (ApplicationService.IsInDesignMode)
{
return;
}
// Create the Action Commands
this.NavigationCommand = new ActionCommand(OnNavigationCommand);
Model = new Model()
{
VisibilityFFUView = "VM"
};
}
#endregion
#region CordisAdapterBase implementation
// Called when the view on which this adapter is located as DataContext is loaded
public override void OnViewAttached(IView view)
{
base.OnViewAttached(view);
}
public override void OnViewDetached(IView view)
{
base.OnViewDetached(view);
}
#endregion
#region NavigationCommand - Command from the view into the ViewModel
public ICommand NavigationCommand { get; set; }
// NavigationCommand event call
// Will be called if one of the buttons in the "AppbarView" view is clicked.
// The command of the model is linked to the button via the Command property.
private void OnNavigationCommand(object commandParameter)
{
if (commandParameter != null)
{
if (ApplicationService.IsInDesignMode) return;
string strParameter = commandParameter.ToString();
switch (strParameter)
{
case "FFU":
Model.VisibilityFFUView = "Turn on";
break;
case "FFUswitch":
Model.VisibilityFFUView = "Turn off";
break;
default:
break;
}
}
else
{
throw new ArgumentNullException(nameof(commandParameter));
}
}
#endregion
}
}
Model
using System.ComponentModel.Composition;
using VisiWin.ApplicationFramework;
namespace HMI.Views.MainRegion
{
[ExportAdapter("Model")]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class Model : ObserverableObject
{
private string _visibilityFFUView;
public string VisibilityFFUView
{
get { return _visibilityFFUView; }
set
{
_visibilityFFUView = value;
OnPropertyChanged();
}
}
}
}
ObserverableObject
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace HMI.Views.MainRegion
{
public class ObserverableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
UPDATE
I have added the NavigationView, and shifted both the DataContext of NavigationView and ZoneFView to XAML to reduce some code..
NavigationView
<vw:View xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
x:Class="HMI.Views.Common.AppbarView"
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:vw="http://inosoft.com/visiwin7" xmlns:local="clr-namespace:HMI"
mc:Ignorable="d" d:DesignWidth="200" d:DesignHeight="638"
DataContext="{vw:AdapterBinding ViewModel}">
<Grid x:Name="LayoutRoot" Background="{DynamicResource AppbarBackgroundBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="8" />
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{DynamicResource AccentBrush}" StrokeThickness="0" />
<StackPanel Grid.Column="0" Margin="10,10,0,0" VerticalAlignment="Top">
<vw:NavigationRadioButton HorizontalAlignment="Stretch" RegionName="MainRegion" ViewName="HomeView" IsChecked="True" Style="{DynamicResource AppbarNavigationRadioButtonStyle}" VerticalAlignment="Top" Height="52" Symbol="{DynamicResource appbar.tiles.nine}" LocalizableText="#Appbar.Dashboard" Margin="0,0,0,0" SymbolHorizontalAlignment="Left" />
<vw:NavigationRadioButton HorizontalAlignment="Stretch" RegionName="MainRegion" ViewName="FFUnitsView" IsChecked="false" Style="{DynamicResource AppbarNavigationRadioButtonStyle}" VerticalAlignment="Top" Height="52" Symbol="{DynamicResource appbar.interface.button}" LocalizableText="#Appbar.FFUnits" Margin="0,10,0,0" BorderThickness="1,1,0,1" CommandParameter="FFU" Command="{Binding NavigationCommand}">
</vw:NavigationRadioButton>
</StackPanel>
</Grid>
</vw:View>
Solution:
Like Clemens said I did use two instances of my DataContext. Therefore, when I did updated my property
I found this post on how to solve this problem: How can I create only one instance of a DataContext for multiple windows? Thanks for the input! Case closed.
I am teaching myself... I cannot understand why the UI won't update when a second class is involved. I am missing something basic and I don't get it.
In the first Class:
I have two ObservableCollections bound to two WPF ListViews, which is bound correctly and works.
I have a Command bound to a Button to move items from one Collection to the other, which works as expected.
In the second Class (backcode) I have implemented "Drag and Drop". On Drop I try to call the same Method (which is in the first Class and is used by the Button/Command. The Command is also in the first class).
On "Drag and Drop" the items are moved from one collection to the other (confirmed with Console.Writeline), however the UI doesn't update like it does with the Button/Command.
I believe the problem is that with "Drag and Drop" I am calling the Method from another class. I thought I could do that, but I must not be doing it right?
I have included everything from 4 files (xaml, backcode, class, relayCommand) so hopefully it is easy to reproduce. Can anyone tell me why & how to get this to work???
<Window x:Class="MultipleClassDragAndDrop.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MultipleClassDragAndDrop"
xmlns:ViewModel="clr-namespace:MultipleClassDragAndDrop.ViewModel"
mc:Ignorable="d"
Title="MainWindow" Height="716" Width="500">
<Window.Resources>
<ViewModel:MultiColumnViewModel x:Key="MultiColumnViewModel"/>
</Window.Resources>
<Grid DataContext="{Binding Mode=OneWay, Source={StaticResource MultiColumnViewModel}}" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Button Content="Test Command" Command="{Binding Test_Command}"/>
</StackPanel>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView1" Background="Black" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView1, UpdateSourceTrigger=PropertyChanged}" MouseMove="ListView1_MouseMove" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightPink" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
<Grid Grid.Row="0" Grid.Column="2" >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="700"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<ListView Grid.Row="1" Grid.Column="0" x:Name="ListView2" Background="Black" MinHeight="300" MinWidth="165" Width="Auto" HorizontalContentAlignment="Center"
ItemsSource="{Binding ActiveJobListView2, UpdateSourceTrigger=PropertyChanged}"
MouseMove="ListView1_MouseMove"
AllowDrop="True" Drop="ListView2_Drop" >
<ListView.ItemTemplate>
<DataTemplate>
<GroupBox BorderThickness="0" Foreground="Black" FontWeight="Bold" Width="150" Background="LightBlue" BorderBrush="Transparent">
<StackPanel Orientation="Vertical" VerticalAlignment="Center" >
<TextBlock Text="{Binding JobID}" FontWeight="Bold" />
<TextBlock Text="{Binding CustomerName}" FontWeight="Bold" />
</StackPanel>
</GroupBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Grid>
BackCode
using MultipleClassDragAndDrop.ViewModel;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
namespace MultipleClassDragAndDrop
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
private void ListView1_MouseMove(object sender, MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.LeftButton == MouseButtonState.Pressed)
{
int lb_itemIndex = ListView1.SelectedIndex;
// Package the data.
DataObject data = new DataObject();
data.SetData("Int", lb_itemIndex);
data.SetData("Object", this);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Move);
}
}
private void ListView2_Drop(object sender, DragEventArgs e)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()}");
base.OnDrop(e);
int index = (int)e.Data.GetData("Int");
// Call A Method In A Different Class
objMultiColumnViewModel.AddAndRemove(index);
e.Handled = true;
}
}
}
My ViewModel Class
using MultipleClassDragAndDrop.ViewModel.Commands;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Windows.Input;
namespace MultipleClassDragAndDrop.ViewModel
{
public class ActiveJob : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
public string _JobID;
public string JobID
{
get { return _JobID; }
set
{ _JobID = value; NotifyPropertyChanged("JobID"); }
}
public string _CustomerName;
public string CustomerName
{
get { return _CustomerName; }
set
{ _CustomerName = value; NotifyPropertyChanged("CustomerName"); }
}
}
public partial class MultiColumnViewModel : INotifyPropertyChanged
{
#region INotifyPropertyChanged
//INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
Debug.WriteLine($"NOTIFY PROPERTY CHANGED! {info}");
}
}
#endregion
//Test Command
private ICommand _Test_Command;
public ICommand Test_Command
{
get
{
if (_Test_Command == null)
{
_Test_Command = new RelayCommand<object>(ExecuteTest_Command, CanExecuteTest_Command);
}
return _Test_Command;
}
}
public bool CanExecuteTest_Command(object parameter)
{
return true;
}
public void ExecuteTest_Command(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
AddAndRemove(0);
Mouse.OverrideCursor = Cursors.Arrow;
}
public void AddAndRemove(int selectedIndex)
{
Debug.WriteLine($"\n\n{System.Reflection.MethodBase.GetCurrentMethod()} Index = {selectedIndex}\n");
ActiveJobListView2.Add(ActiveJobListView1[selectedIndex]);
ActiveJobListView1.RemoveAt(selectedIndex);
foreach (var item in ActiveJobListView1)
{
System.Console.WriteLine($"ActiveJobListView1: {item.JobID}, {item.CustomerName}");
}
System.Console.WriteLine($" ");
foreach (var item in ActiveJobListView2)
{
System.Console.WriteLine($"ActiveJobListView2: {item.JobID}, {item.CustomerName}");
}
}
public MultiColumnViewModel()
{
ActiveJobListView1 = new ObservableCollection<ActiveJob>();
ActiveJobListView2 = new ObservableCollection<ActiveJob>();
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB100", CustomerName = "Smith" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB101", CustomerName = "Jones" });
ActiveJobListView1.Add(new ActiveJob { JobID = "JOB102", CustomerName = "Black" });
}
#region Properties
private ObservableCollection<ActiveJob> _ActiveJobListView1;
public ObservableCollection<ActiveJob> ActiveJobListView1
{
get { return _ActiveJobListView1; }
set
{
_ActiveJobListView1 = value;
NotifyPropertyChanged("ActiveJobListView1");
}
}
private ObservableCollection<ActiveJob> _ActiveJobListView2;
public ObservableCollection<ActiveJob> ActiveJobListView2
{
get { return _ActiveJobListView2; }
set
{
_ActiveJobListView2 = value;
NotifyPropertyChanged("ActiveJobListView2");
}
}
#endregion
}
}
When binding to a Collection, there are 3 kinds of ChangeNotification you need:
The Notification that informs the UI if something was added or removed from the Collection. That is the only kind of Notification ObservableCollection provides.
The Notification on the property exposing the ObservableCollection. Due to case 1 binding and the lack of a "add range", it is a bad idea to do bulk-modifications of a exposed List. Usually you create a new list and only then Expose it to the UI. In your case those would be the properties "ActiveJobListView1" and it's kind.
The Notification on every property of every type exposed in the collection. That would be "ActiveJob" in your case.
Some of those are often forgotten, with Case 2 being the most common case. I wrote a small introduction into WPF and the MVVM pattern a few years back. maybe it can help you here: https://social.msdn.microsoft.com/Forums/vstudio/en-US/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2/lets-talk-about-mvvm?forum=wpf
You have issues with different instances of the same class.
Change:
MultiColumnViewModel objMultiColumnViewModel = new MultiColumnViewModel();
To:
var objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
and it should work
EDIT:
What you are doing is strongly against MVVM principals.
EDIT-2
I had to do some modification to you code to make it work:
In your XAML:
<Window.DataContext>
<local:MultiColumnViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
In your MainWindow.cs:
public MainWindow()
{
InitializeComponent();
objMultiColumnViewModel = this.DataContext as MultiColumnViewModel;
}
private MultiColumnViewModel objMultiColumnViewModel;
Question: ObservableCollection not updated to UI when property changed
What I have tried:
XAML view:
<UserControl x:Class="FlexilineDotNetGui.Flexiline.UserControls.UCRealestate"
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"
xmlns:translations="clr-namespace:FlexilineDotNetGui.Flexiline.Translations"
xmlns:viewModels="clr-namespace:FlexilineDotNetGui.Flexiline.ViewModels"
x:Name="Realestate"
DataContext="{StaticResource vmRealestate}">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<DataGrid Grid.Row="0" Grid.RowSpan="3" Grid.Column="0" Height="200" Margin="5"
ItemsSource="{Binding Panden, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False"
CanUserAddRows="False" IsReadOnly="True" SelectedItem="{Binding Pand}">
<DataGrid.Columns>
<DataGridTextColumn Header="{x:Static translations:UCRealestate.RegistrationType}" Binding="{Binding RegistrationType, Mode=TwoWay}" Width="Auto"/>
</DataGrid.Columns>
</DataGrid>
<Button Grid.Column="1" Grid.Row="0" Background="Transparent"
Width="20" Height="20" Padding="0" Margin="5" Command="{Binding AddPandCMD}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModels:RealestateViewModel}}}">
<Image Source="/Flexiline;component/Resources/New_16x16.ico"/>
</Button>
<Button Grid.Row="1" Grid.Column="1" VerticalAlignment="Top"
Width="20" Height="20" Padding="0" Background="Transparent" Margin="5" Command="{Binding DeletePandCMD}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type viewModels:RealestateViewModel}}}">
<Image Source="/Flexiline;component/Resources/Delete_16x16.ico"/>
</Button>
<Grid Grid.Row="3" Margin="5">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1" Margin="0,5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MaxWidth="100"/>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
</Grid>
<Label Grid.Row="2" Grid.Column="2" Content="{x:Static translations:UCRealestate.RegistrationType}" HorizontalContentAlignment="Right" VerticalAlignment="Center"/>
<ComboBox Grid.Row="2" Grid.Column="3" Margin="5" ItemsSource="{Binding AardInschrijving, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding SelectedAardInschrijving, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Description" SelectedValuePath="FlexKey"/>
</Grid>
</Grid>
</Grid>
</Grid>
</Grid>
</UserControl>
ViewModel :
using FlexilineDotNet.SharedDomainLogic.Models.CommunicationModels;
using FlexilineDotNetGui.Domain.Controls;
using FlexilineDotNetGui.Domain.Models;
using GalaSoft.MvvmLight.Command;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Data;
namespace FlexilineDotNetGui.Flexiline.ViewModels
{
public class RealestateViewModel : ANavigationPaneSubViewModel, INotifyPropertyChanged
{
BorgRealestate _borgRealestate = new BorgRealestate();
ObservableCollection<BorgRealestate> ocPanden = new ObservableCollection<BorgRealestate>();
private DomainController _controller = DomainController.GetInstance();
public RelayCommand<object> AddPandCMD { get; private set; }
public RelayCommand<object> DeletePandCMD { get; private set; }
#region "properties"
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
//public ObservableCollection<BorgRealestate> Panden { get; }
public BorgRealestate Pand
{
get
{
return _borgRealestate ?? (_borgRealestate = new BorgRealestate());
}
set
{
if (value == _borgRealestate) return;
_borgRealestate = value;
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public List<DropDownItem> AardInschrijving { get { return _controller.GetDropDownItemsByType("AardInschrijving"); } }
private DropDownItem SelectedAardInschrijvingDI
{
get
{
DropDownItem test = _controller.GetDropDownItemsByType("AardInschrijving").FirstOrDefault(x => x.FlexKey == _borgRealestate?.RegistrationType);
if (test == null)
{
test = _controller.GetDropDownItemsByType("AardInschrijving").FirstOrDefault();
}
return test;
}
set
{
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public int SelectedAardInschrijving
{
get
{
return SelectedAardInschrijvingDI.FlexKey;
}
set
{
if (value == Pand?.RegistrationType) return;
Pand.RegistrationType = value;
OnPropertyChanged();
OnPropertyChanged("Panden");
}
}
public string SelectedAardInschrijvingText
{
get
{
return SelectedAardInschrijvingDI.Description;
}
}
#endregion
#region "ctor"
public RealestateViewModel()
{
//Panden = new ObservableCollection<BorgRealestate>();
AddPandCMD = new RelayCommand<object>(o => AddPand());
DeletePandCMD = new RelayCommand<object>(o => DeletePand());
}
#endregion
#region "methods"
private void AddPand()
{
BorgRealestate newBorgRealestate = new BorgRealestate { RegistrationType = SelectedAardInschrijving };
Panden.Add(newBorgRealestate);
}
private void DeletePand()
{
Panden.Remove(Pand);
}
#endregion
}
}
Problem description:
When I Update the combobox value, it's updated in the "Panden" property when I check with a breakpoint but the datagrid in the view is not updated. The problem exists with Oneway and also with TwoWay as I defined in datagrid columns. I have both modes tried.
The views are inside a dxnavbar control, when I switch between navbar items and back then the view is updated OK.
EDIT:
When the application starts, the Panden list is indeed null, I had forgotten to mention something....
In my view there is a datagrid with a button where I add items to the observable collection where also some of the properies as example nature inscription combobox value is shown in datagrid. This button is then connected to a relay command. That is the collection that i have binded to the datagridview.
the getter of the property property is not empty and is therefore filled with the correct values, only the changes do not change in the view.
it is indeed normal that the collection is null if no "Pand" items are added to the "Panden" collection via the provided button on the UI (relaycommand). But the "Pand" item in de datagrid will not update after i change a value from the combobox after adding an item with the button to the collection.
EDIT 21/11 08:54
This is the logic for adding the pand:
private void AddPand()
{
BorgRealestate newBorgRealestate = new BorgRealestate { RegistrationType = SelectedAardInschrijving };
Panden.Add(newBorgRealestate);
}
But after i add i see the items gets added to the collection and get also updated in the collection after the combobox value changed only not in ui. (when the row in the datagrid have the focus)
In this code here:
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
You create a new collection but you don't at that point call OnPropertyChanged; and you shouldn't because it would be wrong to do this here. Therefore, the UI doesn't know you've created the new collection. It looks like the first time the above getter is called is in your AddPand function. Therefore, the UI never receives an OnPropertyChanged for Panden and so does not update.
Instead of this create the collection in your constructor and you should find it will update. Also you may not need a setter at all for Panden if it is never recreated.
in constructor:
Panden = new ObservableCollection<BorgRealestate>();
Then Panden property becomes:
public ObservableCollection<BorgRealestate> Panden { get; }
Which can be simplified to:
public ObservableCollection<BorgRealestate> Panden { get; } = new ObservableCollection<BorgRealestate>();
In the Viewmodel:
That short piece of code did the trick:
CollectionViewSource.GetDefaultView(ocPanden).Refresh();
Replace this:
public ObservableCollection<BorgRealestate> Panden
{
get
{
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
With this:
public ObservableCollection<BorgRealestate> Panden
{
get
{
if(ocPanden != null)
{
CollectionViewSource.GetDefaultView(ocPanden).Refresh(); //This will do the trick
}
return ocPanden ?? (ocPanden = new ObservableCollection<BorgRealestate>());
}
set
{
if (ocPanden == value)
return;
ocPanden = value;
OnPropertyChanged("Panden");
}
}
Unfortunately I have not yet found out where the cause really comes from?
If someone can know that then leave a message?
Hi Learning MVVM and I am bit confused, I am trying to display my model name in a list and here what I have.
Model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel;
namespace Morza.Model
{
public class DBEnvironment : INotifyPropertyChanged
{
private String _Name;
private String _HostName;
private String _HostPath;
public DBEnvironment(String __name, String __hostname, String __hostpath)
{
Name = __name;
HostName = __hostname;
HostPath = __hostpath;
}
public String Name {
get
{
return _Name;
}
set
{
_Name = value;
OnPropertyChanged("Name");
}
}
public String HostName
{
get
{
return _HostName;
}
set
{
_HostName = value;
OnPropertyChanged("HostName");
}
}
public String HostPath
{
get
{
return _HostPath;
}
set
{
_HostPath = value;
OnPropertyChanged("HostPath");
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
ModelView
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Morza.Model;
using System.Collections.ObjectModel;
namespace Morza.ViewModel
{
internal class LoginViewModel : ViewModelBase
{
ObservableCollection<DBEnvironment> _DBEnvironments;
private DBEnvironment _obj;
public LoginViewModel()
{
DBEnvironmentInfo = new ObservableCollection<DBEnvironment>();
DBEnvironmentInfo.Add(new DBEnvironment("T2", "127.0.0.1", "C:"));
DBEnvironmentInfo.Add(new DBEnvironment("T2", "127.0.0.1", "C:"));
DBEnvironmentInfo.Add(new DBEnvironment("T2", "127.0.0.1", "C:"));
DBEnvironmentInfo.Add(new DBEnvironment("T2", "127.0.0.1", "C:"));
}
public DBEnvironment DBEnvironment {
get
{
return _obj;
}
set
{
_obj = value;
}
}
public ObservableCollection<DBEnvironment> DBEnvironmentInfo
{
get
{
return _DBEnvironments;
}
set
{
_DBEnvironments = value;
OnPropertyChanged("PersonsInfo");
}
}
}
}
View
<Window x:Class="Morza.View.Login"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Login" Height="260" Width="425" WindowStartupLocation="CenterScreen" Background="LightBlue" ResizeMode="NoResize">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="10"></RowDefinition> <!-- 0 Nothing -->
<RowDefinition Height="25"></RowDefinition> <!-- 1 Label Instruction -->
<RowDefinition Height="10"></RowDefinition> <!-- 2 Nothing -->
<RowDefinition Height="25"></RowDefinition> <!-- 3 Username -->
<RowDefinition Height="10"></RowDefinition> <!-- 4 Nothing -->
<RowDefinition Height="25"></RowDefinition> <!-- 5 Password -->
<RowDefinition Height="10"></RowDefinition> <!-- 6 Nothing -->
<RowDefinition Height="25"></RowDefinition> <!-- 7 Environment -->
<RowDefinition Height="10"></RowDefinition> <!-- 8 Nothing -->
<RowDefinition Height="25"></RowDefinition> <!-- 9 Label Warning -->
<RowDefinition Height="10"></RowDefinition> <!-- 10 Nothing -->
<RowDefinition Height="25"></RowDefinition> <!-- 11 Button-->
<RowDefinition Height="10"></RowDefinition> <!-- 12 Nothing -->
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="25"></ColumnDefinition>
<ColumnDefinition Width="150"></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Name="lblInstruction" Content="Enter a Username, Password, and Environment" Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" HorizontalAlignment="Center"></Label>
<Label Name="lblUsername" Grid.Column="1" Grid.Row="3" Content="Username:" HorizontalAlignment="Right"></Label>
<TextBox Name="txtUsername" Grid.Column="3" Grid.Row="3"></TextBox>
<Label Name="lblPassword" Grid.Column="1" Grid.Row="5" Content="Password:" HorizontalAlignment="Right"></Label>
<PasswordBox Name="pwdPassword" Grid.Column="3" Grid.Row="5"></PasswordBox>
<Label Name="lblEnv" Grid.Column="1" Grid.Row="7" Content="Environment:" HorizontalAlignment="Right"></Label>
<ListView Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="7" ItemsSource="{Binding DBEnvironmentInfo}" >
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding DBEnvironmentInfo.Name}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Label Name="lblWarning" Content="" Grid.Column="1" Grid.ColumnSpan="3" Foreground="Red" Grid.Row="9" HorizontalAlignment="Center"></Label>
<Button Name="btnLogin" Content="Login" Grid.Column="1" Grid.Row="11"></Button>
<Button Name="btnCancel" Content="Exit" Grid.Column="3" Grid.Row="11" IsCancel="True"></Button>
</Grid>
</Window>
When running this code, the issue is the grid is blank. I am not using any framework that I am aware and don't really want to. If there something I am missing.
Looks like you forgot to set the DataContext of the Window:
<Window
...
xmlns:ViewModels="clr-namespace:Morza.ViewModel">
<Window.DataContext>
<ViewModels:LoginViewModel />
</Window.DataContext>
...
Also, another thing to mention is that you do not need to add the property name in your ListView DataTemplate:
<Label Content="{Binding Name}" />
This is because your DataTemplate is bound to a single object in the list.
I have made a UserControl with a DependencyProperty and I would like to bind 2 way. But somehow this doesn't work. The "City"-property never gets set in the AddressViewModel when the property changes.
This is my UserControl:
XAML:
<UserControl x:Class="EasyInvoice.UI.CityPicker"
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="30" d:DesignWidth="300"
DataContext="{Binding CityList, Source={StaticResource Locator}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBox IsReadOnly="True" Margin="3" Text="{Binding SelectedCity.PostalCode, Mode=OneWay}" Background="#EEE" VerticalContentAlignment="Center" HorizontalContentAlignment="Right"/>
<ComboBox Margin="3" Grid.Column="1" ItemsSource="{Binding Path=Cities}" DisplayMemberPath="CityName" SelectedItem="{Binding SelectedCity}"/>
</Grid>
</UserControl>
Code behind:
using EasyInvoice.UI.ViewModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace EasyInvoice.UI
{
/// <summary>
/// Interaction logic for CityPicker.xaml
/// </summary>
public partial class CityPicker : UserControl
{
public CityPicker()
{
InitializeComponent();
((CityListViewModel)this.DataContext).PropertyChanged += CityPicker_PropertyChanged;
}
private void CityPicker_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "SelectedCity")
SetCurrentValue(SelectedCityProperty, ((CityListViewModel)this.DataContext).SelectedCity);
}
public static readonly DependencyProperty SelectedCityProperty =
DependencyProperty.Register("SelectedCity", typeof(CityViewModel), typeof(CityPicker),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public CityViewModel SelectedCity
{
get
{
return (CityViewModel)GetValue(SelectedCityProperty);
}
set
{
SetCurrentValue(SelectedCityProperty, value);
}
}
}
}
This is where I use this control:
<UserControl x:Class="EasyInvoice.UI.NewCustomerView"
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"
Height="135" Width="450"
xmlns:xctk="clr-namespace:Xceed.Wpf.Toolkit;assembly=Xceed.Wpf.Toolkit"
xmlns:einvoice="clr-namespace:EasyInvoice.UI">
<UserControl.Resources>
<Style TargetType="xctk:WatermarkTextBox">
<Setter Property="Margin" Value="3"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="5"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="10"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Voornaam" Text="{Binding FirstName}"/>
<xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Famillienaam" Grid.Column="2" Text="{Binding LastName}"/>
<xctk:WatermarkTextBox Grid.ColumnSpan="2" Watermark="Straat" Grid.Row="2" Text="{Binding Address.Street}"/>
<xctk:WatermarkTextBox Grid.ColumnSpan="1" Watermark="Huisnummer" Grid.Column="2" Grid.Row="2" Text="{Binding Address.HouseNr}"/>
<xctk:WatermarkTextBox Grid.ColumnSpan="1" Watermark="Busnummer" Grid.Column="3" Grid.Row="2" Text="{Binding Address.BusNr}"/>
<einvoice:CityPicker Grid.Row="3" Grid.ColumnSpan="4" SelectedCity="{Binding Address.City, Mode=TwoWay}"/>
<Button Grid.Row="5" Content="Opslaan en sluiten" Grid.ColumnSpan="4" Style="{StaticResource PopupButton}"/>
</Grid>
</UserControl>
And this is the ViewModel for "Address":
using EasyInvoice.Model;
using GalaSoft.MvvmLight;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EasyInvoice.UI.ViewModel
{
public class AddressViewModel : ViewModelBase
{
private string _street;
private string _houseNr;
private string _busNr;
private CityViewModel _city;
public AddressViewModel(Address address)
{
LoadAddress(address);
}
public AddressViewModel() : this(new Address()) { }
private Address Address { get; set; }
public string Street
{
get
{
return _street;
}
set
{
if (_street == value)
return;
_street = value;
RaisePropertyChanged("Street");
}
}
public string HouseNr
{
get
{
return _houseNr;
}
set
{
if (_houseNr == value)
return;
_houseNr = value;
RaisePropertyChanged("HouseNr");
}
}
public string BusNr
{
get
{
return _busNr;
}
set
{
if (_busNr == value)
return;
_busNr = value;
RaisePropertyChanged("BusNr");
}
}
public string BusNrParen
{
get
{
return string.Concat("(", BusNr, ")");
}
}
public bool HasBusNr
{
get
{
return !string.IsNullOrWhiteSpace(_busNr);
}
}
public CityViewModel City
{
get
{
return _city;
}
set
{
if (_city == value)
return;
_city = value;
RaisePropertyChanged("City");
}
}
public void LoadAddress(Address address)
{
this.Address = address;
if(address == null)
{
_street = "";
_houseNr = "";
_busNr = "";
_city = new CityViewModel(null);
}
else
{
_street = address.StreetName;
_houseNr = address.HouseNr;
_busNr = address.BusNr;
_city = new CityViewModel(address.City);
}
}
}
}
When I'm debugging, I can see that this line is reached when I change the property in my UI:
SetCurrentValue(SelectedCityProperty, ((CityListViewModel)this.DataContext).SelectedCity);
But somehow, this never gets set:
public CityViewModel City
{
get
{
return _city;
}
set
{
if (_city == value)
return;
_city = value;
RaisePropertyChanged("City");
}
}
Also, I'm sure the viewmodel is wired up correctly, because I set a breakpoint at "HouseNr" and this works correctly.
Just in case there is not enough provided, the project can be found here: https://github.com/SanderDeclerck/EasyInvoice
From your code
public CityViewModel SelectedCity
{
get
{
return (CityViewModel)GetValue(SelectedCityProperty);
}
set
{
SetCurrentValue(SelectedCityProperty, value);
}
}
Change this
SetCurrentValue(SelectedCityProperty, value);
to this
SetValue(SelectedCityProperty, value);
Issue is in your binding. You have set DataContext of UserControl to CityListViewModel so binding is failing since binding engine is searching for property Address.City in CityListViewModel instead of AddressViewModel.
You have to explicitly resolve that binding using RelativeSource or ElementName.
SelectedCity="{Binding DataContext.Address.City,RelativeSource={RelativeSource
Mode=FindAncestor, AncestorType=UserControl}, Mode=TwoWay}"
OR
Give x:Name to UserControl say NewCustomer and bind using ElementName.
SelectedCity="{Binding DataContext.Address.City, ElementName=NewCustomer}"
Also you can avoid setting Mode to TwoWay since you have already specified that at time of registering of DP.