WPF - Why is my property not being updated? - c#

Here's some code:
<ListBox
MaxWidth="468"
SelectionMode="Extended"
ItemsSource="{Binding Visitors, Mode=TwoWay}">
<ListBox.ItemContainerStyle>
<Style TargetType={x:Type ListBoxItem}>
<Setter Property="IsSelected" Value="{Binding VisitorSelected, Mode=TwoWay}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
I need Visitors to be updated (at the source) whenever a visitor is selected. For some reason, What I have doesn't work. I'm racking my brain, but I can't think of a way around this. Ideas?
EDIT: Sorry, I accidentally had a duplicated property in there, which I removed. Also, let me clarify something: VisitorSelected IS getting updated. However it's not doing me much good, because I need to be notified in the view model containing Visitors - not where VisitorSelected is defined. I hope this makes sense

I ended up adding an Action parameter to the visitor item type, then calling that when VisitorSelected is set. The Action is passed in by the view model.

Related

ItemsControl, ItemContainerStyle and ItemTemplate

Since I'm still struggling with understanding how ItemContainerStyle works, I tried to go to the root component that defines its behavior, that is ItemsControl.
The simplest application of style I can think of is trying to apply a couple of settings, let say the Background and the Foreground to the item.
<Window.DataContext>
<local:VM></local:VM>
</Window.DataContext>
<DockPanel >
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Foreground" Value="red"/>
<Setter Property="Control.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</Window>
The underlying class for the data is:
public class VM
{
public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>();
public VM()
{
Items.Add("first");
Items.Add("second");
Items.Add("third");
}
}
The result:
Ok, background is not applied, but this is not what I wanted to check and BTW in WPF there seem to be more exceptions than rules. (And BTW2 i've already fighted with assigning the background of a ListBox selected item, that requires to retemplate the whole thing, maybe here it's similar? If you know the answer it's appreciated, but I leave it for now because it's taking me off track).
Let's also have a look at the Visual Tree:
That is, for ItemsControl the items don't get a 'wrapper element'; if we do the same with a ListBox, for each item of the collection, it will be constructed a ListBoxItem.
Now let's try to template the item by adding this (just after </ItemsControl.ItemContainerStyle>) :
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<Label MaxWidth="100" Content="{Binding}"/>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
This is the result (they are moved in the center because of the MaxWidth="100"; I wanted to see if there was something behind):
The style is not applied anymore. Let's have a look at the Viusal Tree:
This visual tree is not surprising, we just replaced default representation that before was a TextBlock. In its place now we find a Label with its own standard sub-tree.
What's surprising is that at least Foreground should apply to the label too, but unfortunately it doesn't.
What's going on then?
I've read a very similar question here:
Can't use ItemTemplate and ItemContainerStyle together?
It differs from this in that it tries to assign the ContentTemplate. Since I'm still struggling with the basic behavior here (and I didn't understand the answer there except that there is some sort of copy-problem) I decided to put this more basic question.
However it seems there is a style-targeting problem here and not a copy problem; this is because if I keep the ItemTemplate, but replace the Label with a TextBlock (that leads to the very same VisualTree of the non-templated version) I get back my foreground red color!
<ItemsControl.ItemTemplate>
<ItemContainerTemplate>
<TextBlock MaxWidth="100" Text="{Binding}"/>
</ItemContainerTemplate>
</ItemsControl.ItemTemplate>
Getting warmer?
So it seems that the framework checks if the component is TextBlock and if not doesn't apply the style.
But this is the default behavior when applying implicit styles: a stile with (TargetType == the type of the control being styled).
In this case it seems like the framework assumes that the TargetType is TextBlock, and never reconsiders this assumption even if ItemTemplate is set.
In order to better understand how the style-target works here I tryed to set the style's TargetType explicitly, who knwos, so let's try this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="Label">
<Setter Property="Label.Foreground" Value="red"/>
<Setter Property="Label.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
See the TargetType="Label"? Great. It gives the error:
Cant apply to ContentPresenter a style intended for Label.
(translated from italian, maybe not the exact wording in english. plz replace with the exact one if you have it at hand).
That is, it expects this:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Label.Foreground" Value="red"/>
<Setter Property="Label.Background" Value="yellow"/>
</Style>
</ItemsControl.ItemContainerStyle>
It somewhat makes sense, because the root node of each item, according to the visual tree shown before is actually ContentPresenter.
At this point I'm quite confused: how is it supposed to work? The idea for the moment is that it doesn't.
The behavior for the subclasses like ListBox seems to be more sensible: it styles the container of the item; here a container for the item doesn't exist. That's just my guess because i couldn't find any documentation saying this.
You're looking at your items and thinking about them when setting the ItemContainerStyle.
But of course this is their container you're setting a style on. The container of each item. You don't really care about your container because it's not doing much.
Maybe a concrete example of a use case would be clearer than theory.
If you look at:
https://i.imgur.com/UZ6Nqrc.png
Those red and blue rectangles are units in this game.
Those are a variety of nato symbols indicating infantry, artillery cavalry etc.
An itemcontainerstyle is used to position them.
The whole panel on the left has an itemscontrol with a canvas as it's itemspanel ( instead of the default stackpanel ).
There is a viewmodel for each unit and a collection of these is bound to the itemssource of that itemscontrol.
A unit viewmodel has an X and Y property which is used to position the unit within that canvas.
The position of a unit is defined by a point which is the centre of it's view. Glossing over exactly why that is, I think this is interesting because the unit's viewmodel doesn't need to calculate the offset from centre to top left corner. This is done by a converter in the view and applied using a style:
<Style TargetType="ContentPresenter" x:Key="CenteredContentPresenter">
<Setter Property="Canvas.Top">
<Setter.Value>
<MultiBinding Converter="{local:MultiAddConverter}">
<Binding Path="Y" Mode="TwoWay"/>
<Binding Path="ActualHeight"
Converter="{local:MultiplyConverter Multiplier=-.5}"
RelativeSource="{RelativeSource Self}"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="Canvas.Left">
<Setter.Value>
<MultiBinding Converter="{local:MultiAddConverter}">
<Binding Path="X" Mode="TwoWay"/>
<Binding Path="ActualWidth"
Converter="{local:MultiplyConverter Multiplier=-.5}"
RelativeSource="{RelativeSource Self}"
Mode="TwoWay" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
Elsewhere, in the map editor trees are positioned in a similar manner.
The ItemContainerStyle is applied to the item containers that gets created by the virtual GetContainerForItemOverride method.
In the ItemsControl base class, this method returns a ContentPresenter:
protected virtual DependencyObject GetContainerForItemOverride()
{
return new ContentPresenter();
}
In the derived ListBox class, it returns a ListBoxItem:
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxItem();
}
The TargetType of the ItemContainerStyle must match the type of the DependencyObject returned from this method. Otherwise, you'll get an exception when the style is applied to the container(s) at runtime.

Hiding TreeView items

I've been trying to just hide items from a TreeView. I'm using a custom data type as source (called SettingsMenuItem) which inherits from FrameworkElement (currently FrameworkContentElement, because otherwise the TreeView renders them wrong).
My goal is by setting the VisibilityProperty of these FrameworkElements to either Collapsed or Visible that I'm able to hide certain items (including their children). I know that this can be done by deleting items from the source collection. But that's not what I want. It would mean that I have to mirror each collection in order to keep track of it's actual items, bind to each one in order to be notified about Visibility-changes and create a new collection each time one changes. A lot of overhead for this.
Right now I have no clue how I could accomplish that. I figure it's related to the ItemsGenerator, but I haven't seen any possibility to override it's behaviour. I thought TreeView would be able to detect Visibility, but obviously it doesn't. As alternative I thought of a custom TreeViewItem (maybe even TreeView if necessary) - but at this point the abstraction of this whole system overwhelms me. I don't know where to start and what is actually necessary to solve the problem.
Tips what I have to change or implement by myself would be more than enough. A complete solution would be nice.
You can do this using a data trigger bound to a property (e.g. "IsVisible") in you tree data nodes:
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="False">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
While this technically answers your question I would be wary of actually doing it. User3690202's comment is correct, it's the sort of thing you would normally do via filtering in your view model.
For alternate solution using code behind xaml.cs:
To Remove a specific TreeViewItem from a TreeView which is created from a code behind.
TreeViewItem treeViewItem1 = new TreeViewItem
{
Visibility = Visibility.Collapsed,
};
use the code with TreeViewItem you want to hide in a if condition to hide specific TreeViewItem Header let say "Cars" and you want to hide it and use the code with if condition to hide "Cars" TreeViewItem.

Too many calls from DataGrid to ItemsSource

I have a WPF application what contains wpf toolkit datagrid.
Here is XAML code of DataGrid control
<wpf_toolkit:DataGrid ItemsSource="{Binding Plans, Mode=OneTime}" Grid.Row="0" x:Name="PlanDataGrid" ColumnWidth="auto" HorizontalScrollBarVisibility="Auto">
<wpf_toolkit:DataGrid.Columns>
<wpf_toolkit:DataGridTextColumn Header="Key Note Name" IsReadOnly="True"
Binding="{Binding Path=KeyNotepad, Mode=OneTime}" />
<wpf_toolkit:DataGridTextColumn Header="SKO Name" IsReadOnly="True"
Binding="{Binding Path=SKOName, Mode=OneTime}" />
<wpf_toolkit:DataGridTextColumn Header="SKO Version" IsReadOnly="True"
Binding="{Binding Path=SKOVer, Mode=OneTime}" />
<-- Other 10 columns with data templates, bindings etc. -->
</wpf_toolkit:DataGrid.Columns>
</wpf_toolkit:DataGrid>
ItemsSource property of DataGrid binds to collection of simple string-based view models.
So, the problem is that DataGrid takes data from items source columns_count * visible_rows_count times (about 27*13 times) what makes terrible application brakings.
Is there any way to reduce number of calls from DataGrid to items source?
Thanks in advance.
Problem that i see is that row virtualization is not enabled. That causes DataGrid to create visual elements for all items in ItemsSource, not only those that are visible.
To enable virtualization you need to set this properties on DataGrid
EnableRowVirtualization="True"
And just to be on the safe side you need to set attached properties from VirtualizingStackPanel
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
VirtualizationMode will cause the DataGrid to reuse visual rows instead of generating new each time you scroll around. Though I'm not sure this is available to you since it looks like you're using WpfToolkit. Are you targetting .NET 3.5 ?
Thanks for reply. Yes. I work with .NET 3.5. I've made controls what contains datagrid static, so some loads ends at applications start. But, when I scroll DataGrid, it loads data, but not so much as at the first time. Some words about virtualization. Data source that binds to DataGrid works like data virtualization container.
Here is link http://www.codeproject.com/Articles/34405/WPF-Data-Virtualization.
My Data Source is similas. Also, I create style for grids, that enable virtualization and link in to datagrid (I forgot to write it in code sample here). But nothing changes. Here is my style and how I link it to DataGrid
<Style x:Key="vd_datagrid_style" BasedOn="{StaticResource {x:Type wpf_toolkit:DataGrid}}"
TargetType="{x:Type wpf_toolkit:DataGrid}">
<Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True"/>
<Setter Property="VirtualizingStackPanel.VirtualizationMode" Value="Recycling"/>
<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="True"/>
</Style>
<wpf_toolkit:DataGrid ItemsSource="{Binding Plans, Mode=OneTime}" Grid.Row="0" x:Name="PlanDataGrid"
Style="{StaticResource vd_datagrid_style}" SelectedItem="{Binding SelectedPlan, Mode=TwoWay}">
Hmm... I'll try to add EnableRowVirtualization="True" to my style.
P.S. Is there any way to check whether the style is connected to the control?

MVVM Execute Command on ViewModel from other ViewModel

I'm struggling for about 14 days now with a simple task: In database, I have definitions for hardware categories. For example :
HDD
Internal
External
Flash
This list is in database defined like this:
[ID - ParrentID - Name] : 1 - 0 - HDD, 2 - 1 - Internal, 3 - 1 - External, 4 - 1 - Flash.
Through Entity Framework I get these rows into my application. From this flat data I then create structured object which is my DataModel. This model is defined as follows :
public class Category
{
private int _id = -1;
private string _name = "";
private List<Category> _subCategories = null;
// property getters and setters, constructors, and bool HasSubCategories
}
Now, from these I create ViewModel called SubCategoryViewModel to which is binded my TreeView. So, I can view my categories in treeview and with my defined and maintained hierarchy. This works just fine. In SubCategoryViewModel is defined a Command through Attached Behavior for MouseDoubleClick which is also binded to TreeView. So, when user doubleclicks on Item, in SubViewCategoryModel defined method will execute particular code. List of SubCategoryViewModel is nested in HWDocumentViewModel which is a main ViewModel for my window.
What I need now is obvious : When user doubleclicks on item in TreeView, I need to load items from database and show them in ListView. My opinion is, that in HWDocumentViewModel I need to define an collection of Items and load them accordingly to selected category in ListView. But, I don't know how to execute a method on HWDocumentViewModel from SubCategoryViewModel. Because : TreeView is binded to list of SubCategoryViewModel items, so when DoubleClick occurs, the method on SubCategoryViewModel is executed. I'm searching for a way, how to execute a method on main ViewModel (HWDocumentViewModel).
I tried this approach :
I created a property : public static SubCategoryViewModel SelectedCategory on HWDocumentViewModel. When doubleclick occurs, I set this property from SubCategoryViewModel as this. So, in this property is object, which executed doubleclick event delegate. Great, now I have in HWDocumentView model an object, which user selected.
So, I need to load items to ListView. But, will I load them from method in SubCategoryViewModel ? I don't think so. Instead I should load them from Main View Model by creating a ViewModel for them and bind it to ListView, right ? But, how can I from SubCategoryViewModel call a method in HWDocumentViewModel ? Should I write a static method
on a HWDocumentViewModel which will be accessible from SubCategoryViewModel ?
Or is there a way, how to call Command defined on HWDocumentViewModel from SubCategoryViewModel ?
Or generally, did I take a right approach to create a Warehouse-like application in WPF ?
Thanks a lot.
EDIT: XAML for my TreeView looks like this :
<TreeView x:Name="tvCategories" Background="White" ItemsSource="{Binding Categories}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Normal" />
<Setter Property="behaviors:MouseDoubleClick.Command" Value="{Binding MouseDoubleClickCommand}" />
<Setter Property="behaviors:MouseDoubleClick.CommandParameter" Value="{Binding}" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type localvm:SubCategoryViewModel}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding CategoryName}" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
I'm not sure I see the problem. You have a tree of subcategories and when one is selected, the appropriate SubCategoryViewModel sets itself as SelectedCategory on the main HWDocumentViewModel. That seems like a reasonable approach.
So why do you need to call a command? Why can't you just load the new list in HWDocumentViewModel in response to a change of its SelectedCategory property (ie in the setter)?
If you really must use a command to invoke the load, then simply keep a reference to your main HWDocumentViewModel in each SubCategoryViewModel, and invoke the command with a simple:
_mainViewModel.LoadCategoryCommand.Execute();
With MVVM and trying to communicate between View and ViewModel or between ViewModels a publisher/Subscriber setup works well or a messaging paradigm like what's found in MVVMLight or Prism. I posted an answer on MVVM Light's messaging setup here
In the message you can send an object that holds any data you would like to send back and forth between the view models.
I highly recomend using a framework when working with mvvm it makes like much easier. MVVM Framework Comparison is a link to an answer that goes through a comparison of some of the major frameworks.

Bug with DataBinding in WPF Host in Winforms?

I've spent far too much time with this and can't find the mistake. Maybe I'm missing something very obvious or I may have just found a bug in the WPF Element Host for Winforms.
I am binding a ListView to a ObeservableList that lives on my ProductListViewModel.
I'm trying to implement searching for the ListView with the general Idea to just change the ObservableList with a new list that is filtered.
Anyway, the ListView Binding code looks like this:
<ListView ItemsSource="{Binding Path=Products}" SelectedItem="{Binding Path=SelectedItem}" SelectionMode="Single">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"></Setter>
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"></TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And the ViewModel code is as vanilla as it can get:
private ObservableCollection<ProductViewModel> products;
public ObservableCollection<ProductViewModel> Products
{
get { return products; }
private set
{
if (products != value)
{
products = value;
OnPropertyChanged("Products");
}
}
}
Now the problem here: Once I debug into my OnPropertyChanged method, I can see that there are no subscribers to the PropertyChanged event (it's null), so nothing happens on the UI..
I already tried Mode=TwoWay and other Binding modes, it seems I can't get the ListView to subscribe to the ItemsSource...
Can anyone help me with this? I'm just about to forget about the ElemenHost and just do it in Winforms
greetings Daniel
Is there any binding error in the output window?
By the way, you should consider getting the collection view wrapping your products, and then filtering the view, instead of replacing the whole collection.
The code would be something like:
var collectionView = CollectionViewSource.GetDefaultView(Products);
collectionView.Filter += item => ...;

Categories

Resources