ListBox Binding with Global Index - wpf

My application has a couple of ObservableCollections, one which is nested within an element of the other. Each contain a number of fields e.g.
ObservableCollectionA (called Listings)
Title
Description
Address
Images As MediaItems
ObservableCollectionB (called MediaItems)
ImageName
Src
DisplayTime
Currently I have been accessing ObservableCollections as follows:
Listings(0).MediaItems(0).ImageName
My application has the main Window display the items from Listings and a UserControl which contains a ListBox which displays the items from MediaItems.
Currently my Window is bound to Listings using code in the New method:
Dim AppLocal As Program = Application.CurrentItem
AppLocal.CurrentItem = 0
Me.DataContext = Listings.Item(AppLocal.CurrentItem)
For the Listings ObservableCollection, the UserControl has a XAML DataContext which references a local method which pulls the records from the nested MediaItems ObservableCollection.
<UserControl.DataContext>
<ObjectDataProvider ObjectType="{x:Type local:ThumbImageLoader}" MethodName="LoadImagesv2" IsAsynchronous="True" />
</UserControl.DataContext>
<Grid x:Name="ThumbListBoxGrid">
<ListBox x:Name="ThumbListBox" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" />
</Grid>
The method is here:
Public NotInheritable Class ThumbImageLoader
Public Shared Function LoadImagesv2() As List(Of MediaItems)
Dim AppLocal As Program = Application.Current
Dim ThumbImages As New List(Of MediaItems)
ThumbImages = Listings(AppLocal.CurrentItem).MediaItems
Return ThumbImages
End Function
End Class
Whilst developing the UI layout I have just been binding the first item (0 index). I now want to be able to set AppLocal.CurrentItem from anywhere in the application so the Window and the ListBox are updated.
Ideally I would like it so when the global property index changes, the UI is updated.
How do I do this?
Is there a better way to go about it?
Ben

Ok, I discovered the joy of CollectionView. Offered exactly what I was after and was excrutiatingly easy to implement. I was blown away at not only how easy it was to implement, but I managed to cut out more lines of code than I used to implement it.
I implemented a public CollectionViewSource
Public ListingDataView As CollectionViewSource
In my main Window, I implemeted it as follows:
<CollectionViewSource x:Key="ListingDataView" />
and bound my top-level Grid to it:
<Grid DataContext="{Binding Source={StaticResource ListingDataView}}">
In my Application Startup I set the CollectionView Source
AppLocal.ListingDataView = CType(Application.Current.MainWindow.Resources("ListingDataView"), CollectionViewSource)
AppLocal.ListingDataView.Source = Listings
The next part which impressed me the most was implementing it for my User Control. I remembered the UserControl is inheriting from the main window so it has access to the CollectionView already, so I ditched the separate Class and Method binding in favour for this:
<ListBox ItemsSource="{Binding Path=MediaItems}" VerticalAlignment="Top" IsSynchronizedWithCurrentItem="True" />
Now whene I want to set the Current List Index, I simply call this:
AppLocal.ListingDataView.View.MoveCurrentToPosition(AppLocal.CurrentProperty)
A few milliseconds later, the UI updates automatically.
Done!!

When you want multiple source data (like your observable collection properties and the index for the observable collection) to a single target you should use MultiBinding.
So in your case somethign like this should help...
<ListBox x:Name="ThumbListBox" IsSynchronizedWithCurrentItem="True" >
<ListBox.ItemsSource>
<MultiBinding Converter="{StaticResource CollectionAndIndexCollaborator}">
<Binding Path="Listings" />
<Binding Path="Application.CurrentItem.CurrentItem" />
</MultiBinding>
</ListBox.ItemsSource>
</ListBox>
provided that ....
Your data context is some class that holds the Application object via a property of same name Application and the Listings collection via property of same name Listings.
Your DataContext class and Application class must have INotifyPropertyChanged implemented. It should also raise notifications for Application and Setter of CurrentItem and CurrentItem.CurrentItem properties.
CollectionAndIndexCollaborator.Convert() method returns the same indexed value as the final collection....
return ((ObservableCollection)values[0]).Item(int.Parse(values[1])) ;
where assuming MyListingType is the T of your Listings collection.
This way when anyone changes the global Application.CurrentItem.CurrentItem the multi binding above will get notified and will select the required Listings item.

Related

ItemsControl DataTemplate when using an arbitrary UserControl collection

Context
I am working on a all-in-one virtual desktop for my users where most tasks require input through a modal dialog. Some super users can navigate through many kinds of configuration dialogs, which leads to serveral occurences of 2nd level dialogs (the first modal dialog invokes another one). In compliance with internal design orientations, each of those dialogs are placed on top of a semi-transparent grayed out background.
Trying to avoid writing the gray-background-panel over and over, I figured I could use an ItemsControl in order to stack my dialogs. My proof of concept worked wonders using a collection of String. So far so good.
The problem
Things get odd when using a collection of UserControl as the ItemsSource. WPF displays the actual UserControl instead of the ItemTemplate. It feels like the template is not even used when the collection's items are UIElements.
Xaml
<ItemsControl ItemsSource="{Binding SomeList}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Background="#AA000000">
<Label Content="Does it work" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Code Behind
Public ReadOnly Property SomeOtherList As UserControl()
Get
Return New UserControl() {New MyControl}
End Get
End Property
Public ReadOnly Property SomeList As String()
Get
Return New String() {"One item"}
End Get
End Property
The actual question
Is there any way to specify a template when the ItemsSource items already possess one? Going further, can we even template a wrapper arround UserControl while leaving the actual UserControl untouched?
I know the whole thing could be cheated using code behind, but relying on VB or C# would not go through code review unnoticed. We're looking for an XAML solution here.
P.S. I am open to new solutions as long as there is a single, unified way to invoke an arbitrary number of dialogs.
After two days of tests I admited the initial approach was not possible, or at least to the extend of my knowledge. I came up with a decent wrapping solution.
Wrapper
<UserControl x:Class="HolderPopup"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Background="#AA000000" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Border Style="{StaticResource ResourceKey=borderBase}" />
<Grid Name="placeHolder" Margin="10" />
</Grid>
</UserControl>
The wrapper's code behind has a constructor to which you pass a UserControl to be placed in placeHolder's children, which lets you use it as shown below:
Usage
Private Shared _popup As ObservableCollection(Of UserControl)
Private Shared ReadOnly Property Popup As ObservableCollection(Of UserControl)
Get
If _popup Is Nothing Then _popup = New ObservableCollection(Of UserControl)
Return _popup
End Get
End Property
Public Shared Sub ModalPush(item As UserControl)
Popup.Add(New HolderPopup(item))
End Sub
Public Shared Sub ModalPop()
If Popup.Count > 0 Then Popup.RemoveAt(Popup.Count - 1)
End Sub
'For WPF binding
Public ReadOnly Property PopupRef As ObservableCollection(Of UserControl)
Get
Return Main.Popup()
End Get
End Property
Any event handler, anywhere in the application can call Main.ModalPush in order to stack a modal window on top of whathever was already there.
While this solution respects the constraints (unified popup handling without forcing some hackish dependency in my popups) I am not entirely satisfied. I feel it should have been possible through templating, which would have the benefit of removing this new wrapper class. All in all, that's an alternative, but not exactly what I was looking for.

wpf source and target binding in two different path

How can I bind a Text property for my TextBox that read from a source but it will store its value to a different target?
Let's say
I have a textbox which is bond to a path in a CollectionViewSource
<Window>
<Window.Resources>
<CollectionViewSource Source="{Binding Source={StaticResource ProgramView}, Path='FK_LevelList_ProgramList'}" x:Key="LevelLookupView" />
</Window.Resources>
<TextBox Name="FeePerTermTextbox" Text="{Binding Source={StaticResource LevelLookupView}, Path='FeePerTerm', Mode=OneWay, StringFormat=c2}"/>
</Window>
When perform save, the value of the TextBox will store to another model that is different from the CollectionViewSource
Thanks
I consider this flawed. What happens if the source gets updated? Should the textbox be overwritten?
The reason for this design is imho, that the UI should reflect the "traits" of the element set as DataContext, therefore i expect it to contain the value i give in the model or in the ui. Now there is of course nothing stopping you from not writing the value in your viewmodel to your model, when receiving the set value from the textbox.
public class Redirecter
{
public string FileName
{
get{return mModel.FileName;}
set{mProxy.FileName = value;}
}
}
But this of course won't work well together with INotifyPropertyChanged. I would use a different approach. Use a model that reflects your ui more. If you open the view fill in this ui model with your settings from model A. If you now save this, save each property into Model B.

Setting the objectinstance to the current data item

I am fairly new to WPF, have been working on finding an answer to this for a couple days without much luck, it seems like there should be a way. I have set up a DataTemplate whose DataType is a custom class of mine. Within the DataTemplate definition, I have set up a resources collection using . I did this because I want to create an ObjectDataProvider that will be available to the controls in the DataTemplate - I want the ObjectInstance of this ObjectDataProvider, to be currently bound data item (teh current instance within a list, of my custom class) - because then I want to be able to run a method on the current data instance - when the user changes the text in a textbox that is part of the DataTemplate. Hard to explain but this should make it clearer, here is my xaml:
<DataTemplate x:Key="TierDisplay" DataType="{x:Type tiers:PopulatedTier}">
<DataTemplate.Resources>
<ObjectDataProvider x:Key="FilteredItems" MethodName="GetDisplayItems">
<ObjectDataProvider.MethodParameters>
<sys:Int32>0</sys:Int32>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</DataTemplate.Resources>
<StackPanel Orientation="Vertical">
<TextBox Name="txtMaxSupplyDays" LostFocus="txtMaxSupplyDays_LostFocus"></TextBox>
<DataGrid ItemsSource="{Binding Source={StaticResource FilteredItems}}" />
</StackPanel>
</DataTemplate>
Each instance of the DataTemplate is bound to an instance of the PopulatedTier class. When the user leaves the textbox, txtMaxSupplyDays, I have code in the code-behind to take the value they have entered, and put it into the first MethodParameter of my ObjectDataProvider (whose key is FilteredItems). This works fine using the C# code-behind below, the code finds FilteredItems and plugs the desired value into the MethodParameter. But I can't figure how to tie FilteredItems into the current instance of PopulatedTier so that its GetDisplayItems will run. (If this worked, then presumably the DataGrid would refresh, using the output of GetDisplayItems as its ItemsSource.) In fact, in the C# below, it finds/recognizes the DataContext property of the textbox (sender) as being an instance of PopulatedTier. But how can I refer to this in the XAML within the ObjectDataProvider definition? THANK YOU and let me know if I can clarify further. Of cousre alternate suggestions are welcome; I'd like to keep as much in the XAML and out of the code-behind as I can.
private void txtMaxSupplyDays_LostFocus(object sender, RoutedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
int value;
bool valueOK = Int32.TryParse(textBox.Text, out value);
if (valueOK)
((ObjectDataProvider)textBox.FindResource("FilteredItems")).MethodParameters[0] = value;
}
You have right thoughts about your code-behind - it have to be as small as possible. Its one of the slogan of MVVM pattern, that is what you need - learn MVVM. Internet have a lot of resources, so it wouldn't be a problem to find it.

How to set a filter for a DataGrid ItemSource via MVVM

I have a DataGrid bound to a CollectionViewSource in XAML.
<Window.Resources>
<local:MainWindowViewModel x:Key="ViewModel"/>
<CollectionViewSource x:Key="cvsEntries"
Source="{Binding LogEntriesStore,
Source={StaticResource ViewModel}}"/>
</Window.Resources>
LogEntriesStore is an ObservableCollection (LogEntry is a DTO that's not important in this discussion)
The DataGrid is declared as:
<DataGrid AutoGenerateColumns="False"
Margin="0"
Name="dataGrid1"
ItemsSource="{Binding Source={StaticResource cvsEntries}}"
IsReadOnly="True">
Now I have context menus on various cells in this DataGrid, to kick off a request for filtering. Right click on a cell, and pick filter to filter all the rows, and show only this particular value.
The MVVM gets the request to filter, but the now the tricky bit. How do I set the filter on the CollectionViewSource?
(as an aside -- this would have been a walk in the park with a Silverlight PagedCollectionView but that doesn't seem to be available in WPF, is that right?)
Very simple. You just need to move the collection view inside the view model:
In MainWindowViewModel define a property of type ICollectionView:
public ICollectionView LogEntriesStoreView { get; private set; }
Right after you have initialized the LogEntriesStore property, you need to initialize the LogEntriesStoreView property with the following code:
LogEntriesStoreView = CollectionViewSource.GetDefaultView(LogEntriesStore);
Then you need to remove the CollectionViewSource from XAML and modify the ItemsSource binding to point to the newly created collection view property:
<DataGrid AutoGenerateColumns="False"
Margin="0"
Name="dataGrid1"
ItemsSource="{Binding LogEntriesStoreView, Source={StaticResource ViewModel}}"
IsReadOnly="True">
That's it. Now you have the access to the collection view inside your view model, where you can modify the filter.
There are several solutions to your problem, but in my opinion, the best solutions are the ones which uses only styles with the standard WPF DataGrid control without inventing a new inherited DataGird type or depending on another third-party control. The followings are the best solutions I found:
Option 1: which I personally use: Automatic WPF Toolkit DataGrid Filtering
Option 2: Auto-filter for Microsoft WPF DataGrid

How to do simple Binding in Silverlight?

I understand that Silverlight 3.0 has binding but just want a simple example on how to use this to read a property from a class.
I have a class called Appointment which as a String property called Location:
Public Property Location() As String
Get
Return _Location
End Get
Set(ByVal Value As String)
_Location = Value
End Set
End Property
With a Private Declaration for the _Location as String of course.
I want a XAML element to bind to this property to display this in a TextElement, but it must be in XAML and not code, for example I want something like this:
<TextBlock Text="{Binding Appointment.Location}"/>
What do I need to do to get this to work?
It has to be a Silverlight 3.0 solution as some WPF features are not present such as DynamicResource which is what I'm used to using.
Just to add that my XAML is being loaded in from a seperate XAML File, this may be a factor in why the binding examples don't seem to work, as there are different XAML files the same Appointment.Location data needs to be applied.
You have two options.
If the "Appointment" class can be used as the DataContext for the control or Window, you can do:
<TextBlock Text="{Binding Location}" />
If, however, "Appointment" is a property of your current DataContext, you need a more complex path for the binding:
<TextBlock Text="{Binding Path=Appointment.Location}" />
Full details are documented in MSDN under the Binding Declarations page. If neither of these are working, make sure you have the DataContext set correctly.
You need something in code, unless you want to declare an instance of Appointment in a resource and bind to that but I doubt thats what you want.
You need to bind the Text property to the Property Path "Location" then assign the DataContext of the containing XAML to an instance of the Appointment:-
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock Text="{Binding Location}" />
</Grid>
Then in the control's load event:-
void Page_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Appointment() { Location = "SomePlace" };
}
Note in this case I'm using the default Page control.
If I'm reading correctly, you need to create an instance of Appointment, set the DataContext of the control to that instance and modify your binding to just say: Text="{Binding Location}"
Also, consider implementing INotifyPropertyChanged on your Appointment class to allow the data classes to notify the UI of property value changes.

Resources