Binding to properties of ObservableCollection-items - wpf

interface ICar
{
UserControl SmallView{ get; }
UserControl CompleteView{ get; }
}
class ViewModel
{
ObservableCollection<ICar> Cars{ get; set;}
ObservableCollection<UserControl> SmallViews{ get; }
ObservableCollection<UserControl> CompleteViews{ get; }
}
XAML
<ItemControl ItemsSource="{Binding SmallViews}"/>
<ItemControl ItemsSource="{Binding CompleteView}"/>
I am adding ICars instances to ViewModel.Cars collection. When that happens I want the two UserControls (small and Complete) to be added in the View (XAML).
-I can get it to work as I want, by setting the ItemsSources in CodeBehind when Cars.CollectionChanged is Raised. But I fear all the collection is redrawn for all items in ItemsSource.. I only want the changes to be added, and I would like en elegant solution without a lot of CodeBehind.
This Codebehind makes it work as intended - but I would like something cleaner somthing with real Binding.
CompleteControls and SmallControls are the names for the ItemControls above, which in this solution has no binding markup :-( .
public CarsView(ViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
UpdateViews(viewModel.Cars);
viewModel.Cars.CollectionChanged += (caller, args) =>
UpdateViews(args.NewItems.Cast<ICar>());
}
private void UpdateViews(IEnumerable<ICar> newCars)
{
foreach (var car in newCars)
{
CompleteControls.Items.Add(car.CompleteView);
SmallControls.Items.Add(car.SmallView);
}
}

I see a couple of flaws in your concepts.
class ViewModel
{
ObservableCollection<ICar> Cars{ get; set;}
// This is wrong for MVVM. You don't need this.
//
// ObservableCollection<UserControl> SmallViews{ get; }
// ObservableCollection<UserControl> CompleteViews{ get; }
}
It will also get rid of this.
UpdateViews(viewModel.Cars);
viewModel.Cars.CollectionChanged += (caller, args) =>
UpdateViews(args.NewItems.Cast<ICar>());
About the ObvservableCollection you need to know 2 things.
If you create it directly from another collection it will not trigger the event.
Your args.NewItems.Cast() will always produce one item only in a collection since the ObservableCollection does not have a AddRange() method.
I see that you came from Winforms. Start by inspecting more about MVVM. it will pay off very fast. You need to remember that if you are doing anything in the code behind with controls, stop doing it, cause your doing it wrong.
You will instantiate the UserControls in XAML.
<Listbox ItemsSource={Binding Cars}>
<Listbox.ItemsTemplate>
<DataTemplate>
<StackPanel>
<my:SmallViews />
<my:CompleteViews />
</StackPanel>
</DataTemplate>
</Listbox.ItemsTemplate>
</Listbox>
You should really avoid ever dealing with any UI elements in code behind

Related

TreeView incorrectly shows items as expanded when using VirtualizationMode.Recycling

Whenever I used TreeView I always had just few nodes and each of them usually had less than 100 items. I never really needed any kind of ui virtualization for that but now for the first time I need it.
The problem appears when using ui virtualization with recycling mode the TreeView seems to expand items even though I never expanded them manually.
I googled the issue and as far I understood recycling mode of virtualization in TreeView the containers get reused.
So I assume that the cause might be applying already expanded reused container to an item which wasn't expanded before.
Here is a simple example:
https://github.com/devhedgehog/wpf/
For those who cannot download code for whatever reason here is basically what I have tried to do with the TreeView.
This is what I have in XAML.
<Grid>
<TreeView ItemsSource="{Binding}" VirtualizingStackPanel.IsVirtualizing="True" VirtualizingStackPanel.VirtualizationMode="Recycling">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Parts}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>
And this is code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
IList<Car> list = new List<Car>();
for (int i = 0; i < 5000; i ++)
{
list.Add(new Car() { Name = "test1" + i });
}
foreach (var car in list)
{
car.Parts = new List<string>();
for (int i = 0; i < 500; i++)
{
car.Parts.Add("asdf" + i);
}
}
this.DataContext = list;
}
}
public class Car
{
public string Name
{
get;
set;
}
public List<string> Parts
{
get;
set;
}
}
I hope somebody can provide me a solution to this issue. Is this a known bug?
I am sorry in case its a duplicate. Futhermore I hope you guys tell me what I did wrong since this is my first post before you downgrade the question.
As you probably know, this problem can be solved easily by using standard recycling mode:
<TreeView VirtualizingStackPanel.VirtualizationMode="Standard" ...>
This shouldn't have too much of an impact on your TreeView's performance, as the tree will still be virtualized and a container will only be created for visible items. The benefits of the recycling mode only come into play when scrolling (when items are both being virtualized and realized), and usually the standard virtualization mode is good enough.
However, in case performance is really critical (or if you really want a solution for this while keeping the recycling mode, or if you're looking to do things the right way), you can use backing data and data binding to solve this problem.
The reason why this problem occurs in the first place is this:
Let's say you have a TreeViewItem which has its IsExpanded property set to true. When it's being recycled, i.e. its data is replaced, its IsExpanded property remains the same because it has no way to know whether it should be expanded or not, because that data is not available anywhere. The only place where it exists is the IsExpanded property of the TreeViewItem, and it's not going to be relevant because that item is being reused along with its properties.
If however you have a viewmodel for each tree item you'll be able to bind each TreeViewItem to the IsExpanded property in your TreeViewItemViewModel (you will have a view model for each tree item) and you will always get the correct value because you've made that data available and bound each item to it.
Your TreeView's ItemsSource will be bound to a collection of TreeViewItemViewModel objects, and your TreeViewItemViewModel class will look something like this:
class TreeViewItemViewModel : INotifyPropertyChanged
{
bool IsExpanded { get; set; }
bool IsSelected { get; set; }
TreeViewItemViewModel Parent { get; }
ObservableCollection<TreeViewItemViewModel> Children { get; }
}
You can find more information on how exactly to create such view model in Josh Smith's excellent article Simplifying the WPF TreeView by Using the ViewModel Pattern.

Bind ListBox to another ListBox

I have 2 listBoxes, if you click an item in the top one, then the bottom one filters to a few results.
I am trying to learn WPF and MVVM and am wondering if this is the correct way to do this. Is this the best way?
Here is what I did:
class VisitInfoViewModel : ViewModelBase
{
List<ServiceType> serviceTypes;
List<ServiceType> allServiceTypes;
public VisitInfoViewModel()
{
ServiceCategories = ServiceCategory.Categories;
allServiceTypes = ServiceType.ServiceTypes;
}
public List<ServiceCategory> ServiceCategories { get; set; }
public List<ServiceType> ServiceTypes
{
get
{
return serviceTypes;
}
}
public ServiceCategory SelectedServiceCategory
{
get { return null; }
set
{
serviceTypes = allServiceTypes.FindAll(st => st.ServiceCategoryGuid.Equals(value.Guid));
OnPropertyChanged("ServiceTypes");
}
}
}
and MainWindow.xaml snippet
<ListBox ItemsSource="{Binding Path=VisitInfo.ServiceCategories}"
SelectedItem="{Binding Path=VisitInfo.SelectedServiceCategory}"
ItemTemplate="{StaticResource listBoxTemplate}"
Height="112"
HorizontalAlignment="Left"
Margin="6,30,0,0"
Name="lbxServiceCategory"
VerticalAlignment="Top"
Width="366" />
<ListBox ItemsSource="{Binding Path=VisitInfo.ServiceTypes}"
ItemTemplate="{StaticResource listBoxTemplate}"
HorizontalAlignment="Left"
Margin="6,0,0,19"
Name="lbxServiceType"
Width="366"
Height="121"
VerticalAlignment="Bottom" />
also, why shouldn't I just add an EventHandler for selectedItemChanged on my listBox?
It seems so much simpler and clearer to use the event handler.
I think it is because if I did that it would no longer by MVVM... is that correct?
What would you do and what are the best practices?
What you are doing is mostly fine - though I would personally make the SelectedServiceCategory a "real" property (with a value that's saved).
The difference with MVVM, and doing it in code behind, is that you're working with data. If you make the "Current Category" change the types, then you're working purely with the data, and not worrying about the UI at all. You can change the category by any mechanism, and the UI will always stay up to date.
I, personally, would suggest writing this more like so:
class VisitInfoViewModel : ViewModelBase
{
List<ServiceType> allServiceTypes;
public VisitInfoViewModel()
{
ServiceCategories = ServiceCategory.Categories;
allServiceTypes = ServiceType.ServiceTypes;
}
// This can use a private setter...
public IEnumerable<ServiceCategory> ServiceCategories { get; private set; }
private ServiceCategory currentCategory;
public ServiceCategory CurrentServiceCategory
{
get { return this.currentCategory; }
set
{
if (this.currentCategory != value)
{
this.currentCategory = value;
ServiceTypesInCurrentCategory = allServiceTypes.Where(st => st.ServiceCategoryGuid.Equals(this.currentCategory.Guid));
OnPropertyChagned("CurrentServiceCategory");
OnPropertyChanged("ServiceTypes");
}
}
}
public IEnumerable<ServiceType> ServiceTypesInCurrentCategory { get; private set; }
}
This provides complete freedom to change the CurrentServiceCategory in code or via Xaml, without any event handlers. It also makes your ViewModel completely data related - you don't know or care what is being used to display this - as long as you have something in your View that sets the CurrentServiceCategory, everything stays synchronized correctly.
also, why shouldn't I just add an EventHandler for selectedItemChanged on my listBox? It seems so much simpler and clearer to use the event handler. I think it is because if I did that it would no longer by MVVM... is that correct?
You can do that, but it's typically a violation of MVVM at this point. The main issue is that you'd be coupling the implementation to that event handler - by doing this, you're basically "locking in" the behavior based on your code in your View for this specific implementation of the View. By keeping it "pure" in terms of MVVM, you're View is free to change (ie: maybe you want to switch to a combobox for the ServiceCategories someday) without touching your ViewModel code at all...

WPF Dependency Property workaround

There are 3 UserControls under a MainWindow. Each control have it's own Save Button. The Mainwindow has a SaveAll button.
The MainWindow has a ContentControl and the content property is binded to the VM. At runtime on ButtonClickCommand, the View is instantiated and assigned to the Content Property.
This SaveAll button will internally call methods associated with UserControls Save button. As such, SaveAll doesn't have it's own Method.
This has to be implemented by DependencyProperty.
I had once seen this scenario implemented in a Business App, but somehow missed the concept behind it.
I can't get what was the logic behind this, but it's a very useful thing.
Now I have to implement this, but i'm missing a small thing, I dont know.
I hope the scenario is clear.
Please help me in this scenario, with code.
Thanks,
VJ
Since you mentioned MVVM, here's what you might be looking for. Mind you, this will be a lot cleaner and easier if you use an MVVM framework such as Caliburn, but for this sample, its just vanilla MVVM:
public class MainViewModel
{
public MainViewModel()
{
ViewOneModel = new SubViewModel();
ViewTwoModel = new SubViewModel();
Children = new List<SubViewModel>(new[] { ViewOneModel, ViewTwoModel });
}
public void SaveAll()
{
foreach(var child in Children)
{
child.Save();
}
}
public IList<SubViewModel> Children { get; private set; }
public SubViewModel ViewOneModel { get; set; }
public SubViewModel ViewTwoModel { get; set; }
}
public class SubViewModel
{
public void Save()
{
}
}
and on the UI you basically have subviews (UserControls) composed in your main view:
<StackPanel>
<Button Width="100" Height="20" Content="Save All" />
<local:ViewOne DataContext="{Binding ViewOneModel}" />
<local:ViewTwo DataContext="{Binding ViewTwoModel}" />
</StackPanel>
You just need to bind the save methods to your buttons using an ICommand interface (preferably RelayCommand instance).
Imho in this scenario there is no need for RoutedEvents. The way I would solve it:
There is a Main-ViewModel that exposes 3 properties with the Sub-ViewModels.
The MainViewModel is the Datacontext for the window, and the subviewmodels bound to the datacontext of the 3 usercontrols.
The sub vm's are exposing a property with a Save-Command. This command is bound to the save buttons in the usercontrols.
The main vm is exposing a property with a saveall-command, which is bound to the SaveAll button.
In the handler of the save all command you are then iterating over the sub-vm's and call save on them.

Binding a save command WPF

I have a window with 3 textboxes in a grid -this is my view- and I have Save button to add a new user to my user list with the datas from the textboxes.
I want to use a relay command to do this on my viewmodel class but I am quite confused with how to make the bindings. I hope it's clear enough. Any ideas, or examples will be helpful.
thanks in advance.
You should have a ViewModel something like the following :
class UserViewModel
{
public String Name { get; set; }
public String Password { get; set; }
public String Email { get; set; }
public RelayCommand AddUserCommand { get; set; }
public UserViewModel()
{
AddUserCommand = new RelayCommand(AddUser);
}
void AddUser(object parameter)
{
// Code to add user here.
}
}
And you can use it like following :
<StackPanel>
<TextBox Text="{Binding Name}"></TextBox>
<TextBox Text="{Binding Password}"></TextBox>
<TextBox Text="{Binding Email}"></TextBox>
<Button Command="{Binding AddUserCommand}">Add</Button>
</StackPanel>
To make this work, put following code in your UserControl/Control/Window's constructor :
DataContext = new UserViewModel();
I presume that you read Josh Smith article: WPF Apps With The Model-View-ViewModel Design Pattern. If you didn't, then read it first, and then download code, because example is very similar to your problem.
Did you created an instance of the ViewModel and putted this instance in the DataContext of your view or stackpanel?
example:
UserViewModel viewModel = new UserViewModel();
UserWindow view = new UserWindow();
view.DataContext = viewModel;
view.Show();
There are several options on coupling the View and the Viewmodel:
Create the View and ViewModel and set the ViewModel to the DataContext property (code above)
Create the ViewModel in the constructor of the View and fill the DataContext property with it
Create a Resource in your view of the type of your ViewModel and fill the DataContext property in XAML
I prefer the first option because you can combine the Views and Viewmodels as you like at runtime.
Hopefully this is a helpfull answer.

How can I add an "IsDirty" property to a LINQ to SQL entity?

I am binding my entities to an edit form in WPF. Within a DataTemplate, I want to be able to set the background color of the root container within a DataTemplate to show it has been changed and these changes have not yet been submitted to the database.
Here's a very simple sample that demonstrates what I'm talking about (forgive errors):
<Page ...>
<Page.DataContext>
<vm:MyPageViewModel /> <!-- Holds reference to the DataContext -->
</Page.DataContext>
<ItemsControl
ItemsSource = {Binding Items}>
<ItemsControl.Resources>
<DataTemplate
DataType="Lol.Models.Item"> <!-- Item is L2S entity -->
<!-- In real life, I use styles to set the background color -->
<TextBlock Text="{Binding IsDirty, StringFormat='Am I dirty? /{0/}'}"/>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</Page>
The example just prints out "Am I dirty? yes" or "Am I dirty? no", but you get the idea.
To do this, I'll need to add a public property to my Item (partial class, simple) that can determine if the entity is dirty or not. This is the tough bit.
public partial class Item
{
public bool IsDirty
{
get
{
throw new NotImplementedException("hurf durf");
}
}
}
Outside of the entity, it's pretty simple (as long as you have the DataContext the entity is attached to). Inside, not so much.
What are my options here?
Edit: I don't think there's one good solution here, so suggestions for workarounds are welcome.
(Okay, similar questions exist, but they are all about how to determine this from outside of the entity itself and use the DataContext the entity is attached to.)
If you are using the dbml generated classes, you should be able to implement a couple of partial methods like this:
public partial class SampleEntity
{
partial void OnCreated()
{
this.IsDirty = true;
}
partial void OnLoaded()
{
this.PropertyChanged += (s, e) => this.IsDirty = true;
this.IsDirty = false;
}
public bool IsDirty { get; private set; }
}

Resources