ViewModel child doesn't refresh proper? binding in WPF - wpf

I have one View that has another one inside. I want to make ViewModel for both of them. But apparently Binding in Child View is not working properly or I have done wrong binding, perhaps.
I have debugged that Child ViewModel is recreated every time I have selected different row in Parent ViewModel.
But UI, doesn't refresh, despite UpdateSourceTrigger=PropertyChanged.
If I edit Binding in XAML while running app then it gets refreshed (as Binding probably is regenerated).
I could set UpdateSourceTrigger=Explicit, but I can't call UpdateSource from none of ViewModels.
PARENT VIEW:
<UserControl ... DataContext="{Binding ProjectsViewModel, Source={StaticResource ViewModelLocator}}">
<Grid>
<poc:AdvancedListView ItemsSource="{Binding Projects}" SelectedObject="{Binding SelectedProject, Mode=TwoWay}"/>
...
<ScrollViewer>
<StackPanel Orientation="Vertical">
...
<poc:Section SectionName="ATTACHMENTS">
<poc:AttachmentsControl DataContext="{Binding AttachmentsViewModel, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" /> // THIS IS BINDING BETWEEN VM
</poc:Section>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
PARENT VIEWMODEL:
public class ProjectsViewModel : BaseViewModel
{
public ProjectsViewModel(ObservableCollection<Project> projects)
{
this.Projects = projects;
}
public ObservableCollection<Project> Projects { get; }
private Project selectedProject;
public Project SelectedProject
{
get { return selectedProject; }
set
{
SetPropertyAndNotify(ref selectedProject, value);
AttachmentsViewModel = new AttachmentsViewModel(selectedProject.Attachments); // THIS IS CREATION OF CHILD VM
}
}
public AttachmentsViewModel AttachmentsViewModel { get; set; }
}
CHILD VIEW:
<UserControl ... x:Name="attachmentControl">
<Grid x:Name="mainGrid">
...
<ListView x:Name="attachmentsListView" ItemsSource="{Binding Attachments, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" SelectionMode="Single"> // THIS IS BINDING TO LIST THAT IS NOT WORKING
<ListView.View>
<GridView>
...
</GridView>
</ListView.View>
</ListView>
</Grid>
</UserControl>
CHILD VIEWMODEL:
public class AttachmentsViewModel : BaseViewModel
{
public ObservableCollection<Attachment> Attachments { get; set; }
public AttachmentsViewModel(ObservableCollection<Attachment> attachments)
{
Attachments = attachments;
}
}
What I do wrong or what concept I have understood wrong?

public class ProjectsViewModel : BaseViewModel
{
public ProjectsViewModel(ObservableCollection<Project> projects)
{
this.Projects = projects;
}
public ObservableCollection<Project> Projects { get; }
private Project selectedProject;
public Project SelectedProject
{
get { return selectedProject; }
set
{
SetPropertyAndNotify(ref selectedProject, value);
// THIS IS CREATION OF CHILD VM
AttachmentsViewModel = new AttachmentsViewModel(selectedProject.Attachments);
}
}
private AttachmentsViewModel _attachmentsViewModel;
public AttachmentsViewModel AttachmentsViewModel
{
get => _attachmentsViewModel;
set => SetPropertyAndNotify(_attachmentsViewModel, value);
}
}
public class AttachmentsViewModel : BaseViewModel
{
// This should be a Read Only property
public ObservableCollection<Attachment> Attachments { get; /* set; */}
public AttachmentsViewModel(ObservableCollection<Attachment> attachments)
{
Attachments = attachments;
}
}
Additional recommendation: Adding extra logic to the property setter - is bad.
Surely in the BaseViewModel implementation there is an opportunity to set the dependence of properties on each other in a different way.

Related

Double Click event is not working on ListBox using MVVM in WPF app

I am facing a problem while binding the double click event on Listbox item.
I am using MVVM and Prism. I didn't understand what is wrong here.
View
<ListBox HorizontalAlignment="Center" ItemsSource="{Binding Cities , Source={StaticResource vmC}}" SelectedItem="{Binding SelectedCity , Source={StaticResource vmC}}" Width="100" Height="200">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}">
<TextBlock.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ItemSelectedCommand}"/>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
ViewModel
public class CityViewModel : ViewModelBase
{
public DelegateCommand ItemSelectedCommand { get; private set; }
public string SelectedCity { get; set; }
public List<string> Cities { get; private set; }
public CityViewModel()
{
ItemSelectedCommand = new DelegateCommand(OnItemSelected);
Cities = new List<string>() { "Bangalore", "New York", "Sydney", "London", "Washington" };
}
private void OnItemSelected()
{
var city = SelectedCity;
}
}
MouseBinding is not part of the visual tree. This means, you cannot use Binding.RelativeSource as there is no tree to traverse.
Instead you must bind to the current DataContext, which is the item's data model. To accomplish this, you would have tzo introduce a model class e.g. City that exposes a Name and ItemSelectedCommand property.
If moving the command to the item model doesn't make sense in your scenario, you should use a RoutedCommand and handle it e.g., in the parent Window. InputBinding is primarily intended to be used in the view only and therefore encourages the use of RoutedCommand.
In your case, it seems you are only interested in notifying the CityViewModel that the selection has changed. In this case simply call OnItemSelected from the SelectedCity property set():
public class CityViewModel : ViewModelBase
{
private string selectedCity;
public string SelectedCity
{
get => selectedCity;
set
{
selectedCity = value;
OnItemSelected();
}
}
public List<string> Cities { get; private set; }
public CityViewModel()
{
...
}
private void OnItemSelected()
{
var city = SelectedCity;
}
}

Nest reactiveui usercontrols wpf / pass ViewModel to usercontrol

Q: How can I bind a ViewModel to a ReactiveUserControl? Or how to nest Reactiveui views?
There's probably something I'm doing wrong, but I can't figure out what exactly.
ReactiveUserControl
// MenuView.xaml
<reactiveui:ReactiveUserControl
x:Class="Views.MenuView"
xmlns:menuItems="clr-namespace:Model"
.... >
<Menu x:Name="RootMenu"
IsMainMenu="True">
<Menu.Resources>
<DataTemplate DataType="{x:Type menuItems:DialogItem}">
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</Menu.Resources>
</Menu>
</reactiveui:ReactiveUserControl>
// MenuView.xaml.cs
namespace Views
{
public partial class MenuView : ReactiveUserControl<MenuViewModel>
{
public MenuView()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
this.OneWayBind(ViewModel,
vm => vm.MenuItems,
view => view.RootMenu.ItemsSource
).DisposeWith(disposables);
});
}
}
}
// MenuViewModel.cs
namespace Views
{
public class MenuViewModel : ReactiveObject
{
public ObservableCollection<DialogItem> MenuItems { get; } = new ObservableCollection<DialogItem>();
public MenuViewModel()
{
MenuItems.Add(new DialogItem("Edit", 224));
MenuItems.Add(new DialogItem("View", 224));
}
}
}
DialogItem represents an item in the menu
// DialogItem.cs
namespace Model
{
public class DialogItem
{
public DialogItem(string description, int dialogId)
{
this.DialogId = dialogId;
this.Description = description;
}
public int DialogId { get; }
public string Description { get; }
}
}
Then finally in MainWindow I include the usercontrol like so:
// MainWindow.xaml
<reactiveui:ReactiveWindow
x:Class="Views.MainWindow"
....
>
<Grid>
<views:MenuView x:Name="MainMenu" />
</Grid>
</reactiveui:ReactiveWindow>
Code behind
// MainWindow.xaml.cs
namespace Views
{
public partial class MainWindow : ReactiveWindow<MainWindowModel>
{
public MainWindow()
{
InitializeComponent();
this.WhenActivated(disposables =>
{
// BIND THE VIEWMODEL CREATED IN THE MAINWINDOW VIEWMODEL, IS THIS CORRECT?
this.Bind(ViewModel,
vm => vm.MainMenuViewModel,
view => view.MainMenu.ViewModel
).DisposeWith(disposables);
});
}
}
}
// MainWindowModel.cs
namespace Views
{
public class MainWindowModel : ReactiveObject
{
public MenuViewModel MainMenuViewModel { get; }
public MainWindowModel()
{
this.MainMenuViewModel = new MenuViewModel();
}
}
}
The Items are iterated, but if I look at the visual representation tree I an ViewModelViewHost item in the ContentPresenter instead of a TextBlock
Here you see the Menu Items are not rendered correctly. (they are rendered but without the text from DataTemplate.
Update:
Using a Menu.ItemTemplate does work, but this is not what I am looking for.
<reactiveui:ReactiveUserControl
x:Class="Views.MenuView"
...
>
<Menu x:Name="RootMenu"
IsMainMenu="True">
<Menu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}" />
</DataTemplate>
</Menu.ItemTemplate>
</Menu>
</reactiveui:ReactiveUserControl>
Discussing this issue on Slack with Glenn Watson gave me the penny drop moment. Because I was using the code behind binding of RxUI, RxUI is using its locator logic to lookup the view. RxUI will lookup a view if there's no ItemTemplate or DisplayPathMember property defined. These views are registered in the Splat container as a view for the viewmodel. So the DataTemplates in <Menu.Resources> are not considered. This is exactly what's shown in the image. A ViewModelViewHost is created, but because no corresponding view is found nothing is displayed.
This can be solved by using the XAML binding instead of the code behind binding (assign the viewmodel to the datacontext to do this!). See the docs for more info.

How to bind and automatically refresh ItemsSource when I'm binding to some Entity from DbSet?

I'm trying to make use of EntityFramework and WPF data binding for the first time.
I have some ListBox. I have set ItemsSource to SomeDbContext.SomeEntity.ToList(); programatically and I have set my binding like this:
<ListBox Name="listbox" Margin="4" SelectedValuePath="Address" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Id}"></Label>
<Label Content="{Binding Address}"></Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Click="ButtonTest_Click">Open</Button>
I'm adding new item to my DbSet and I expected that my list will refresh after SomeDbContext.SaveChanges(); method call, but it didn't.
My Window code behind:
DatabaseContext _dbContext = new DatabaseContext();
public MainWindow()
{
InitializeComponent();
lb.ItemsSource = _dbContext.Addresses.ToList();
// I have tried to set source to _dbContext
}
private void ButtonTest_Click(object sender, RoutedEventArgs e)
{
_dbContext.Addresses.Add(new Adresses() { Address = "192.168.1.2:502" });
_dbContext.SaveChanges();
}
Here is my Entity:
public class Adresses
{
public int Id { get; set; }
public string Address { get; set; }
}
My DbContext:
public class DatabaseContext : DbContext
{
public DbSet<Adresses> Addresses { get; set; }
}
What I am doing wrong?
I guess that my approach is wrong, because I'm creating new object when I'm setting ItemsSource to _dbContext.Addresses.ToList();, but I have no idea how to bind directly to my DbSet (or is it possible).

ListBox DataTemplate for dynamically loaded type

I have a sample MVVM WPF application and I'm having problems creating DataTemplates for my dynamically loaded model. Let me try explain:
I have the following simplified classes as part of my Model, which I'm loading dynamically
public class Relationship
{
public string Category { get; set; }
public ParticipantsType Participants { get; set; }
}
public class ParticipantsType
{
public ObservableCollection<ParticipantType> Participant { get; set; }
}
public class ParticipantType
{
}
public class EmployeeParticipant : ParticipantType
{
public EmployeeIdentityType Employee { get; set; }
}
public class DepartmentParticipant : ParticipantType
{
public DepartmentIdentityType Department { get; set; }
}
public class EmployeeIdentityType
{
public string ID { get; set; }
}
public class DepartmentIdentityType
{
public string ID { get; set; }
}
Here is how my View Model looks like. I created a generic object Model property to expose my Model:
public class MainViewModel : ViewModelBase<MainViewModel>
{
public MainViewModel()
{
SetMockModel();
}
private void SetMockModel()
{
Relationship rel = new Relationship();
rel.Category = "213";
EmployeeParticipant emp = new EmployeeParticipant();
emp.Employee = new EmployeeIdentityType();
emp.Employee.ID = "222";
DepartmentParticipant dep = new DepartmentParticipant();
dep.Department = new DepartmentIdentityType();
dep.Department.ID = "444";
rel.Participants = new ParticipantsType() { Participant = new ObservableCollection<ParticipantType>() };
rel.Participants.Participant.Add(emp);
rel.Participants.Participant.Add(dep);
Model = rel;
}
private object _Model;
public object Model
{
get { return _Model; }
set
{
_Model = value;
NotifyPropertyChanged(m => m.Model);
}
}
}
Then I tried creating a ListBox to display specifically the Participants Collection:
<ListBox ItemsSource="{Binding Path=Model.Participants.Participant}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
The problem is:
I don't know how to create a template that can handle both type of ParticipantTypes, in this case I could have EmployeeParticipant or DepartmentParticipant so depending on that, the data binding Path would be set to Employee or Department properties accordingly
I though about creating a DataTemplate for each type (e.g. x:Type EmployeeParticipant) but the problem is that my classes in my model are loaded dynamically at runtime so VisualStudio will complain that those types don't exist in the current solution.
How could I represent this data in a ListBox then if my concrete types are not known at compile time, but only at runtime?
EDIT: Added my test ViewModel class
You can still create a DataTemplate for each type but instead of using DataType declarations to have them automatically resolve you can create a DataTemplateSelector with a property for each template (assigned from StaticResource in XAML) that can cast the incoming data item to the base class and check properties or otherwise determine which template to use at runtime. Assign that selector to ListBox.ItemTemplateSelector and you'll get similar behavior to what DataType would give you.
That's not a good view-model. Your view-model should be view-centric, not business-centric. So make a class that can handle all four cases from a visual perspective, then bridge your business classes over to that view-model.
EDIT:
Working off your code:
<ListBox ItemsSource="{Binding Path=Model.Participants}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<TextBlock Text={Binding Id} />
<TextBlock Text={Binding Name} />
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I changed the binding, I assume that was a mistake?
I would create a ViewModel for Participant:
public class Participant_VM : ViewModelBase
{
private string _name = string.Empty;
public string Name
{
get
{
return _name ;
}
set
{
if (_name == value)
{
return;
}
_name = value;
RaisePropertyChanged(() => Name);
}
private string _id= string.Empty;
public string Id
{
get
{
return _id;
}
set
{
if (_id== value)
{
return;
}
_id = value;
RaisePropertyChanged(() => Id);
}
}
}
Modify the ListBox as follows.
<ListBox ItemsSource="{Binding Model.Participants.Participant}">
<ListBox.Resources>
<DataTemplate DataType="{x:Type loc:DepartmentParticipant}">
<Grid>
<TextBlock Text="{Binding Department.ID}"/>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type loc:EmployeeParticipant}">
<Grid>
<TextBlock Text="{Binding Employee.ID}"/>
</Grid>
</DataTemplate>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Expander Header="IdentityFields">
<!-- WHAT TO PUT HERE IF PARTICIPANTS HAVE DIFFERENT PROPERTY NAMES -->
<ContentPresenter Content="{Binding }"/>
</Expander>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Edit:
loc refers to the namespace in which the DepartmentParticipant and EmployeeParticipant are present. Hope you are familiar with adding namespaces.

How can I bind an ObservableCollection to TextBoxes in a DataTemplate?

I am trying to successfully TwoWay bind an ObservableCollection to TextBoxes in a DataTemplate. I can get the data to display properly, but I am unable to change the list data through the UI. I have a Model class named 'model' which contains an ObservableCollection named 'List'. The class implements the INotifyPropertyChanged interface. Here is the xaml for the shell. The DataContext for Window1's grid is set to "theGrid.DataContext=model"
<Window x:Class="BindThat.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BindThat"
Title="Window1" Height="300" Width="300">
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
This is the code for the Model class:
class Model : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<string> _list = new ObservableCollection<string>();
public ObservableCollection<string> List
{
get { return _list; }
set
{
_list = value;
NotifyPropertyChanged("List");
}
}
public Model()
{
List.Add("why");
List.Add("not");
List.Add("these?");
}
}
Could anyone advise if I am going about this the correct way?
You need a property to bind two way, so string is not good for this.
Wrap it in a string object, like this:
public class Model
{
public ObservableCollection<StringObject> List { get; private set; }
public Model()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"},
};
}
}
public class StringObject
{
public string Value { get; set; }
}
and bind to Value property instead of "."
Also, you don't need to notify of a change in observable collection, so until your model has some other propertis of its own, it does not need to have INotifyPropertyChange. If you want your ItemsControl react to changes in the individual StringObjects, then you should add INotifyPropertyChanged to a StringObject.
And yet again, two way binding is default, so you need only
<TextBox Text="{Binding Path=Value}" />
in your binding.
I believe you need to derive your collection items from DependencyObject for TwoWay binding to work. Something like:
public class DependencyString: DependencyObject {
public string Value {
get { return (string)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register("Value", typeof(string), typeof(DependencyString), new UIPropertyMetadata(""));
public override string ToString() {
return Value;
}
public DependencyString(string s) {
this.Value = s;
}
}
public class Model {
private ObservableCollection<DependencyString> _list = new ObservableCollection<DependencyString>();
public ObservableCollection<DependencyString> List {
get { return _list; }
}
public Model() {
List.Add(new DependencyString("why"));
List.Add(new DependencyString("not"));
List.Add(new DependencyString("these?"));
}
}
...
<StackPanel x:Name="theGrid">
<GroupBox BorderBrush="LightGreen">
<GroupBox.Header>
<TextBlock Text="Group" />
</GroupBox.Header>
<ItemsControl ItemsSource="{Binding Path=List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</GroupBox>
</StackPanel>
xaml view:
<ItemsControl ItemsSource="{Binding List}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
in code behind in the constructor:
DataContext = new ViewModel();
in ViewModel Class:
class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private ObservableCollection<StringObject> _List = new ObservableCollection<StringObject>();
public ObservableCollection<StringObject> List
{
get { return _List; }
set
{
_List = value;
NotifyPropertyChanged("List");
}
}
public ViewModel()
{
List = new ObservableCollection<StringObject>
{
new StringObject {Value = "why"},
new StringObject {Value = "not"},
new StringObject {Value = "these"}
};
}
}
public class StringObject
{
public string Value { get; set; }
}
Be careful with a collection with type string it doesn't work, you have to use an object => StringObject

Resources