Find Listbox in DataTemplate inside ItemsControl - c#

To create my own drophandler I need to get access to the listbox which is inside an ItemsControl.
XAML
<ItemsControl ItemsSource="{Binding Days}" Name="myCalendar" Margin="200,75,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Rows="6" Columns="7">
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<!-- ItemTemplate -->
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Date}">
</TextBlock>
<ListBox Name="Scenes" ItemsSource="{Binding Scenes}" dd:DragDrop.IsDragSource="True" dd:DragDrop.IsDropTarget="True" dd:DragDrop.DropHandler="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock>
<Run Text="{Binding Path=SlugLine}"/>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
How do I get access or find the ListBox inside the ItemsControl from my ViewModel, not via code behind?

You absolutely do not want the VM knowing about the view. The whole point of MVVM is to decouple the view and the logic.
Instead handle the drop in the code-behind. Some people seem to believe that there should be no code-behind in MVVM, but it's absolutely fine as long as it's specific to the view, and there is no VM logic in there.
Imagine that you have hooked up a completely new view to your VM, say a console based text view. If your view logic remains intact with a completely new view, since it's all in the VM then you're fine. If you have logic in the code-behind that would disappear when you changed views, then you need that logic moved down to the VM.
Drag and drop is fine. You handle the drop in code-behind and then call the VM to do the logic associated with the drop, say via a bound command. If replacing the view with a text view, the drop could be CTRL-V instead, but the same VM command would be called to do the logic associated with the drop.
As mentioned, one way to call the VM from the code-behind would be to have a dependency property on the view that gets bound to a command in the VM, with your code-behind just invoking the command via the property.
A simpler way is to just cast the DataContext to your VM type and call a function directly. A lot of people dislike this since it couples the view to a VM type, but I see no issue with it at all. The view is already coupled to all bound properties on the VM anyway. VM's should be view agnostic, but the view NEEDS to know about the VM in order to be useful.

Related

WPF: binding through custom dependency property

I have an TabControl bound to a Dictionary and has a custom control as it's ContentTemplate. The custom control has a custom dependency property Schedules and it's DataContext is bound to a ViewModel, Here is how it look like:
Main control:
<TabControl Grid.Row="1" ItemsSource="{Binding Schedules}">
<TabControl.ContentTemplate>
<DataTemplate>
<TabControl >
<TabItem Header="Scheduled flights">
<views:MyViewer Schedules="{Binding Value}"/>
</TabItem>
</TabControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
In MyViewer, I also have an DataGrid that I want it to be bound the Schedules passed from the TabControl, but in the same time MyViewer has a ViewModel assigned to it. This is how it looks like in MyViewer:
<DataGrid Grid.Row="1" ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}, Path=Schedules}" AutoGenerateColumns="False" >
So far this is not working, when MyViewer is loaded Schedules property is null. But even if it works, I would want the Schedules to be passed to the ViewModel not code behind. One idea is to populate the Dictionary with ViewModels of MyViewer, but I do not wish to do this, I only want the Main control to know about details of MyViewer. So any clean idea to solve this?
EDIT:
The proposition above does work after changing to ObservableDictionary, but the question remains, how to have the Schedules in the ViewModel
If MyViewer has it's own ViewModel, you should not rather do hacks like this:
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=UserControl}, Path=Schedules}"
This way MyViewer is tightly coupled to some other control.
Im my opinion, in this case, MyViewer's ViewModel should have it's own property Schedules. How could you keep TabControl ViewModel's Schedules and MyViewer ViewModel's Schedules in sync? It depends on your system, but you could try with this ideas:
Sending ViewModel level messages like in MVVM Light, when adding or removing items. Example in this blog post
Try to implement some kind of store like in NgXs or NgRx in Angular
Maybe you don't need to keep Schedules in sync - depends on your system? :)

WPF TabControl not firing Loaded event of unfocused children

I have a TabControl with a binding for the ItemsSource property. It's bound to a ObservableCollection. When I add an item to the collection, the tabs get created correctly, but only the first tab gets its Grid_Loaded event fired. I'm guessing this is because it's focused. I need to initialize stuff when a new tab opens, it contains a control that needs to be referenced.
<TabControl x:Name="tabSessions"
ItemsSource="{Binding Sessions}"
SelectedIndex="0"
BorderThickness="0"
Padding="0,0,0,0">
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type vm:MyViewModel}">
<TextBlock Margin="4,4,16,4" Text="{Binding Name}"></TextBlock>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<Grid Loaded="Grid_Loaded">
<!-- View here -->
</Grid>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Is there an event that I could use to initialize this? I also need the DataContext to be set, and the view to be initialized.c#
The DataTemplate is reused by the tabs, which is why you're probably only seeing one Loaded event. It's only loaded the first time and then reused when you switch tabs. Only the binded content is changed.
Instead of listening to Loaded on the Grid, you could rather listen to DataContextChanged which will tell you every time the DataContext is set to a new object.
The DataContext will change when you switch tabs. The current tab will be set as the DataContext of the DataTemplate (and by extensions the Grid).
When using a DataTemplate you assume that the view is going to be the same, but the content is going to differ. If this is not the case and the views will differ based on the content, you'll probably want to look into using a DataTemplateSelector. This will let you define several DataTemplates and select one of them to use based on the current DataContext. You can read up on DataTemplateSelector in the Microsoft Docs

DisconnectedItem in .NET 4.5.1

I have been searching for the DisconnectedItem issue over the Internet and I have learned that it should have been a solved problem in 4.5 release of .NET Framework. I am currently using 4.5.1 and it's my first time to face this kind of malfunction. The scenario is I have a ViewModel and a View. After an entity changed event (nHibernate) I dispose the old ViewModel, create a new one of the same type and attach it to already existing View (resolved form the Unity container). The problem is that the View has an ItemsControl with ItemsSource bound to the ViewModel List. After attaching the new ViewModel, the DelegateCommand refreshes the CanExecute methods. At first I get the objects, but than comes a sequence of DisconnectedItems (so even though I return false if DisconnectedItem, I'd get all buttons blocked). Is there any other way of changing the DataContext without creating a brand new View?
Here is the "after entity changed" part. The reportFilesCollection is bound to ItemsControl:
_detailsViewModel.Dispose();
_detailsViewModel = new DetailsViewModel(reportFilesCollection, _unityContainer);
IDetailsView view = GetViewOfTypeFromRegion<IDetailsView>();
view.ViewModel = _detailsPreviewViewModel;
And the View part:
<StackPanel x:Name="reportDataPart">
<ItemsControl ItemsSource="{Binding ReportFiles}"
Style="{StaticResource IconDataTableStyle}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl Style="{StaticResource IconDataStyle}"
Tag="PrinterPath">
<Label ToolTip="Report generation time"
Content="{Binding CreatedOn, StringFormat={}{0:g}}"/>
<Button Content="Sign report"
Command="{Binding DataContext.SignReportCommand, ElementName=reportDataPart}"
CommandParameter="{Binding RelativeSource={RelativeSource Self}, Path=DataContext}"/>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
The second ItemsControl (the inner one) provides a special style for the row of data. The absence of it doesn't make any difference. After I reenter the View, all of the buttons are active again but DisconnectedItems are still being present.

WPF ItemsControl binding not updating when bound to an ObservableCollection in an object in the View Model

Basically I have an object in my view model that contains an ObservableCollection of a custom object. My XAML's DataContext is set to my ViewModel, my ViewModel contains a 'Scratchdisk' object, and the Scratchdisk object contains an ObservableCollection of Frame objects. Both the Scratchdisk and the Collection are set up as DependencyProperties.
In short: XAML --DataContext--> EditorViewModel --DependencyProperty--> Scratchdisk --DependencyProperty--> ObservableCollection<Frame>
The Frame object has 3 standard properties: Index, Image, and ImageUrl.
I'm trying to bind to the ObservableCollection in my XAML using this code:
<ItemsControl DataContext="{Binding Source=ThumbnailScratchdisk}" ItemsSource="{Binding Frames, UpdateSourceTrigger=PropertyChanged}" ItemTemplate="{StaticResource ThumbnailTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"></VirtualizingStackPanel>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
Where ThumbnailTemplate is defined in Window Resources as:
<DataTemplate x:Key="ThumbnailTemplate">
<Image Width="128" Height="96" Source="{Binding ImageUrl}"/>
</DataTemplate>
Theoretically, what should happen is, the Scratchdisk should receive filenames, create Frame objects, add them to the Collection, and then the binding should display them. The ObservableCollection is working and being populated, but the binding doesn't seen to be updating. All the updatable properties are set as DependencyProperties so the binding should update shouldn't it?
Links to the files:
XAML
ViewModel
Scratchdisk
Frame
The problem is in the binding of the DataContext of your ItemsControl.
You're setting it to "{Binding Source=ThumbnailScratchdisk}", but what you (presumably) want is to set it to just "{Binding ThumbnailScratchdisk}".
The DataContext of the page is already an instance of EditorViewModel, and you want the DataContext for the ItemsControl to bind to the property ThumbnailScratchdisk of that viewmodel.
Trying changing the binding path in XAML to ThumbnailScratchdisk.Frames

MVVM solution for wpf

Here's the situtation:
Data Context of the window is: MainViewModel.
It's built from ObservableCollections of SubViewModel.
Each SubViewModel has its own ObservableCollection of type String.
Now, I have a treeview. The ItemsSource is the ObservableCollection of the MainViewModel.(Means it is the SubViewModel collection).
I want that if an item is selected, then there will be displayed the ObservableCollection(type String) of the selected Item in the treeview.
How can I do that?
Some code:
<TreeView ItemTemplate="{DynamicResource TreeViewDataTemplate}" ItemsSource="{Binding SubViewModelCollection}"/>
I want to display the collection in a stack panel because of some reasons.
So: (TypeCollection is the string ObservableCollection of the item, it is currently not working of course)
<ItemsControl ItemsSource="{Binding TypeCollection}" x:Name="UserList" ItemTemplate="{StaticResource TemplateDataTemplate}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal" HorizontalAlignment="Left" Grid.Column="1"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I've been struggling alot with that, how can I achieve my target?
Bind the treeview ItemsSource to the UserList's SelectedItem.
<TreeView ItemTemplate="{DynamicResource TreeViewDataTemplate}"
ItemsSource="{Binding ElementName=UserList Path=SelectedItem.SubViewModelCollection}"/>
Assuming the items in UserList are type SubViewModel which has an IEnumerable<T> property called SubViewModelCollection.
I would recommend taking a look at Prism, particularly the portion pertaining to Event Aggregation. What this will allow you to do is publish an event in your application when an item in the TreeView is selected, consume that event elsewhere in your application and bind the ItemControl to the selected SubViewModel all without having to introduce any unnecessary coupling between these two pieces of your application.

Categories

Resources