Silverlight databinding to itemsource in parent's parent datacontext - silverlight

I have a datagrid where in one of the column's header I would like to have a dropdown that filters the data in the grid. The issue being that the datacontext that has the values that would be in this dropdown is in the usercontrol's viewmodel not the datagrids itemssource so the list doesn't seem to be available to the dropdown.
<sdk:DataGridTemplateColumn.HeaderStyle>
<Style TargetType="sdk:DataGridColumnHeader">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Assignee" />
<ComboBox x:Name="cboAttorneyHdr" ItemsSource="{Binding Path=Attorneys}"
Margin="3,0,0,0" SelectedItem="{Binding Path=SelectedAttorney, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</sdk:DataGridTemplateColumn.HeaderStyle>
I found an example using relative source for WPF that seems to be asking the same thing but it seems that this doesn't work for Silverlight. I have tried setting this manually in the code behind but the combobox does appear to be available there either!

One way I've found around this problem is to use some helpers as detailed here - it's just one of the possible implementations, but it amounts to emulating the WPF RelativeSourceBinding with AncestorLevel/AncestorType which is still not available in SL4. Or you could try to google 'silverlight combobox in datagrid' for more ways to solve it, I'm sure you can imagine it's a pretty common problem :)

I found this solution which actually ended up working great though it's going to take me a bit to actually understand what the heck it's really doing.
http://weblogs.asp.net/dwahlin/archive/2009/08/20/creating-a-silverlight-datacontext-proxy-to-simplify-data-binding-in-nested-controls.aspx

Related

Binding a DataGrid when already having a DataContext declared in a Window----WPF

I am running into an issue that I haven't been able to resolve and although I've looked at a few similar posts, I haven't found anything that explains my situation.
Basically I have a WPF Window:
<Window x:Class="NewGame">
<DataContext="{Binding RelativeSource={RelativeSource Self}}"/>
In the class I implement INotifyPropertyChanged to utilize bindings for some properties I have set up to update dynamically using XAML. For instance, I have a DB that has Primary, Secondary and Trim Colors(Hex codes) listed for teams, and the properties automatically will update based on changing the team. So I have the BorderBrush, Foreground and Backgrounds on various things auto-updating in XAML using:
<Foreground="{Binding Path=MyPrimColor}">
<Background={Binding Path=MySecColor}">
<BorderBrush={Binding Path=MyTrimColor}">
, etc...each could be any of the properties, it doesn't matter, those are all working fine.
Now, I have a DataGrid which I need to bind to a DataTable to display the players on the team, and that is where I have run into the issue. It tells me the "Items Collection must be empty before using Itemsource" and throws an exception. This was never an issue until I started using the databindings in XAML, when I had things set in code behind, everything worked fine, but I also know this isn't the way things are supposed to be done, which is why I want to have it working with the XAML data-bindings.
I created MyDT a property as DataTable, and when I try to bind
<DataGrid DataContext="{Binding Path=MyDT}">, it causes the Foreground and Background binding paths to try to bind to the Data.DataTable object as well which obviously throws an error.
I have seen some say I need to use <DataGrid.DataContext> inside the <datagrid> but I haven't gotten that to work either. I understand where the problem is coming from---I already have the bindings set at a higher level, but I just don't know how to fix it in XAML by only binding the DataGrid to the DT property while leaving the others to bind to the class level.
Here is the full code section in XAML:
<DataGrid x:Name="TeamRosterDT"
Height="400"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Foreground="{Binding Path=MyTrimColor}"
RowBackground="{Binding Path=MySecColor}"
AlternatingRowBackground="{Binding Path=MyPrimColor}"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeColumns="False"
ColumnWidth="Auto"
HorizontalScrollBarVisibility="Visible"
ItemsSource="{Binding}"
Opacity="0.8"
VerticalScrollBarVisibility="Visible"
Visibility="Hidden"
DataContext="{Binding Path=MyDT}">
<DataGridColumnHeader Style="{StaticResource DataGridHeaderStyle}" />
<DataGridCell HorizontalContentAlignment="Center" />
</DataGrid>
no need to change DataContext (DataContext="{Binding Path=MyDT}"), only bind ItemsSource (<DataGrid ItemsSource="{Binding Path=MyDT}"> or <DataGrid ItemsSource="{Binding Path=MyDT.DefaultView}">)
Exception is thrown because of incorrect items declaration (lines with DataGridColumnHeader, DataGridCell). They are added to Items list which is not supported when ItemsSource is set
I got the Exceptions resolved(I believe) by using:
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="{Binding Path=MyPrimColor}" />
<Setter Property="Foreground" Value="{Binding Path=MyTrimColor}" />
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
<DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="HorizontalContentAlignment" Value="Center" />
</Style>
</DataGrid.CellStyle>
However, the DataTable does not update properly---when the XAML is initialized its an empty DB, which is then filled and filtered according to what team is selected. When a new team is selected, the DT is simply filtered, not refilled. However, since I suppose this technically isn't a "change" to the DT itself, it doesn't fire the OnPropertyChanged event. How can I get it to update properly using XAML triggers, or is than an event I can utilize when the DB refilters?
Is where I would use an ObservableCollection?

Track where I clicked in ListView MVVM

I am writing a folder browser and I want to open folders on double click.
My folders are binded to ListView with GridView inside and I am tracking double click like this:
<i:EventTrigger EventName="MouseDoubleClick">
<Custom:EventToCommand Command="{Binding FolderOpenedCommand, Mode=OneWay}" CommandParameter="{Binding SelectedItem, ElementName=FolderView}"/>
</i:EventTrigger>
But I have an annoying issue: if I double click on gridview splitter to autosize column, it will also open selected folder, which I am not want to.
So, I have several options for now: put event handler inside style and use it with code behind or leave it as is, but in my case I want to do implement it with MVVM scenario because codebehind is not suitable for me.
My question is: how I can send my parameter as SelectedItem only if I click on the item and null when I click on something else?
I want to track this to make a proper behavior as far as I cannot apply double click to gridview on some reason.
Could someone please help me with this problem?
EDIT:
Lets clarify one thing to be sure we speak about the same things:
I can define something like this
<Style x:Key="itemStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource MetroListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="FolderView_OnMouseDoubleClick"></EventSetter>
</Style>
Bu I cannot do like this:
<Style x:Key="itemStyle" TargetType="{x:Type ListViewItem}" BasedOn="{StaticResource MetroListViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="{Binding OpenFilesCommand}"></EventSetter>
</Style>
Because It will lead to exception. Now I want to understand how I can apply command here if Handler does not accept command? Do I need to write some attached property?
Avoid using listview. Use DataGrid instead. Then you can add eventtrigger to row style. ListView is obsolete class introduced in wpf 3 that was replaced by datagrid in wpf 4 an there no reason to use it anymore.
Another option is to use use custom behaviour implemented as attached property, e.g. InvokeCommandOnRowDoubleClick attached to Grid. To learn more about attached behaviours read this: http://blogs.msdn.com/b/dgartner/archive/2009/11/11/wpf-attached-behavior-example-watermark-text.aspx
There is a MouseDoubleClick property on the ListViewItem control. You can rework your style to contain a correct event when double clicking an item, apply it only to the ListViewItems and it won't listen to that event when not double clicking the gridview splitter.
You can read more about this here.
Well, to solve this issue I had to use InputBindings property for each control in each gridview column. I put Grid over controls and made like this:
<GridViewColumn Header="Size (Bytes)">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Grid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.FolderOpenedCommand}"
CommandParameter="{Binding ElementName=FolderView, Path=SelectedItem}"></MouseBinding>
</Grid.InputBindings>
<TextBlock Text="{Binding Path=Size, StringFormat='{}{0:#,#.}'}"/>
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Unfortunately for my case I didnt find better solution

WPF TabControl tabs losing state information

I'm fairly new to WPF and am almost positive I've just overlooked something. I'm having issues preserving the state of user controls within my tabs when I select. It seems very similar to this question but it didn't seem to get resolved.
In my main ViewModel I have an ObservableCollection of the abstract TabableViewModel class. It also implements the INotifyPropertyChanged for the TabControl's SelectedIndex.
I want the TabControl's content to be automatically created so I used a DataTemplate like so:
<Window.Resources>
<DataTemplate DataType="{x:Type vm:EasyViewModel}">
<UserControls:EasyControl/>
</DataTemplate>
<DataTemplate DataType="{x:Type vm:ComplicatedViewModel}">
<UserControls:ComplicatedControl/>
</DataTemplate>
<DataTemplate x:Key="TabHeaderTemplate">
<Grid>
<TextBlock Text="{Binding HeaderText}" />
</Grid>
</DataTemplate>
<Style x:Key="TabItemStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}" />
<Setter Property="HeaderTemplate" Value="{StaticResource TabHeaderTemplate}" />
<Setter Property="Content" Value="{Binding}" />
</Style>
</Window.Resources>
<TabControl x:Name="contentTab" TabStripPlacement="Bottom"
SelectedIndex="{Binding SelectedTabIndex, Mode=TwoWay}"
ItemsSource="{Binding TabItems, Mode=TwoWay}"
ItemContainerStyle="{StaticResource TabItemStyle}">
</TabControl>
EasyControl and ComplicatedControl both have a number of text boxes, combo boxes and other fields. Both ViewModels extend the TabableViewModel class and implement INotifyPropertyChanged.
In a typical test the ObservableCollection will contain one instance of EasyViewModel and then two instances of ComplicatedViewModel. The tabs build themselves properly, but the state of the ViewModels is not being maintained.
If I make changes inside EasyControl, switch to the first ComplicatedControl tab and then switch back, all data has been lost.
If I make changes to the first ComplicatedControl and switch to the second, I get those same changes instead of a blank slate. If I then switch to EasyControl and back again, all data is once again cleared.
I've seen quite a few examples where the DataTemplates are basic, but I haven't seen any where UserControls are picked based on ViewModel type. Is there anything special I have to do to maintain state?
I don't want to break the MVVM pattern by creating the UIElements within the ViewModels. I'm also hoping that I don't have to extend any Tab controls to get this working, but I will if I have to. I'm surprised it's this hard to do.
So you have a situation where your UI seems to be losing data. This is generally not possible when using MVVM because your data is in your view model and not the view. Therefore, if it is being reset at any point, then it is your code that is resetting it. You said:
My problem comes from switching between tabs
So at the point when you switch tabs, you must initialise one of your view models and that's why it loses its data. You're the only one that can fix this problem... it is not reproducible from your code example. Search for calls to your view model constructor and you should find your problem.

Expander with Virtualization inside WPF Datagrid

I have a performance issue in a prototype I am working on. The requirement is to build a datagrid with multiple synchronized frozen panes, supporting grouping and sorting etc... For more details about the grid I am building, see this previous question.
Now, I have a question related to Grouping and in particular Expanders. I have a GroupStyle defined by the following Xaml, and taken from this blog post.
<!--Default GroupStyle-->
<GroupStyle x:Key="gs_Default">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=Name}" FontWeight="Bold" Padding="3"/>
</StackPanel>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander x:Name="exp"
BorderBrush="#FFA4B97F"
BorderThickness="0,0,0,1"
IsExpanded="{Binding Path=Items[0].IsExpanded}">
<Expander.Header>
<DockPanel TextBlock.FontWeight="Bold">
<TextBlock Text="{Binding Path=Name}" Margin="5,0,5,0" />
<TextBlock Text="{Binding Path=ItemCount}"/>
</DockPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
The Expander is not virtualized and we are experiencing a performance issue when there are several hundred rows in a group.
Has anyone encoutered this before and have a fix? I am ideally looking for a Virtualizing Expander, and have seen workarounds such as this (which doesn't solve the problem).
If you're using .NET 4 then get the ItemsPresenters visibility to track/be dependent on the IsExpanded state.
WPF DataGrid Virtualization with Grouping
In .NET 4.5 there's a new attached property VirtualizingPanel.IsVirtualizingWhenGrouping.
Another alternative is to push the grouping into the ViewModel rather than making the DataGrid/CollectionView be responsible for that. See here:
http://blog.smoura.com/wpf-toolkit-datagrid-part-iv-templatecolumns-and-row-grouping/
In order to solve this, we did the following.
We used DataTemplates to present a different row view for different row viewmodels. We had a GroupRowViewModel and ItemRowViewModel. Also a parent ViewModel which had a sorted list of Group/Item viewmodels.
When the grid was instantiated, the parent ViewModel would sort all child viewmodels into the following:
Group
Item
Item
Item
Group
Item
Item
Item
When a GroupRow is clicked you want to execute some code where the parent (which contains a sorted list of group+item rows) will remove or include the items. E.g. say the second gropu was clicked, your list of rowviewmodels you bind to now becomes
Group
Item
Item
Item
Group (Collapsed)
That's it. So no magic at all, you manually remove or include the rows you want depending on what was clicked. It worked with virtualization and hundreds of thousands of rows at an acceptible speed.
Sorry I can't post any code (due to NDA) but I hope that helps you. Also - I would suggest looking at Telerik Grid as this is awesomely fast for large datasets

WPF how to make a listbox/listview unfocusable

I've been trying for a while to display some data in a listbox/listview that would be unfocusable (I mean not only the list, but also the items in it).
I tried with both types of list (listbox and listview), and I used their ItemTemplate and ItemContainerStyle. Everywhere I could, I set the Focusable property to false.
I don't see any other way than disabling the list, but then I have to change all its style, to make it appear not disabled.
Have I missed something? Is there a read-only type of list that I don't know about?
Thank you for your ideas :)
The problem you're probably seeing is that each individual item in the list is focusable. However, you can override that... Try adding this to your listbox:
<ListBox.ItemContainerStyle>
<Style TargetType="Control">
<Setter Property="Focusable" Value="False" />
</Style>
</ListBox.ItemContainerStyle>
Note however that this makes the items unselectable (by keyboard or by mouse). You can set the selected item programmatically, but it doesn't appear to be highlighted automatically any more - so really, this behaves almost the same as an ItemsControl.
Use an ItemsControl with TextBlocks instead of a ListBox
<ItemsControl ItemsSource="{Binding MyListBoxItemsSource}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding MyDisplayName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Resources