I have a typical MVVM scenario: I have an ItemsControl bound to an ObservableCollection of StepsViewModels. I define a DataTemplate so that StepViewModels are rendered as StepViews. The same happens in the StepView: I have an ItemsControl to an ObservableCollection of ParameterViewModels with a DataTemplate to render them as ParameterViews.
My problem is: I have to refresh the ItemsControl to render items added and remove items. I can refresh the ItemsControl with StepViews, because I have access to it and can call ItemsControl.Items.Refresh(). But how can I access the StepViews so I can call a Refresh method? ItemsControl Items are StepViewModels...
Here is the code:
<UserControl x:Class="mylib.EditorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:mylib">
<UserControl.Resources>
<DataTemplate DataType="{x:Type my:StepViewModel}">
<my:StepView HorizontalAlignment="Stretch"/>
</DataTemplate>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="27"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Row="1" VerticalAlignment="Top">
<StackPanel Orientation="Vertical">
<TextBlock Name="lblHelpRefresh" Margin="0 7 0 7" Visibility="{Binding HelpVisible}"
VerticalAlignment="Center" HorizontalAlignment="Center"
FontSize="9pt" Foreground="#949494"
Text="Please click refresh to reload this list"/>
<ItemsControl Name="stkStepContent"
ItemsSource="{Binding Steps}"/>
</StackPanel>
</ScrollViewer>
</Grid>
</UserControl>
How can I access the StepView controls that render the stkStepContent Items?
if you have an ObservableCollection then the adding and removing of items should refresh automatically. But if you just updated a property in your StepsViewModel then, StepsViewModel should be the one to alert the update either by implementing the INotifyPropertyChanged interface or by inheriting from the DependencyObject and using Dependency properties instead.
Anyway, if you really want to have the ability to manually refresh your collection, use a CollectionViewSource in your ViewModel. Then call the View.Refresh() method of your CollectionViewSource.
To access your views in the codebehind you can use the DataContext of the control and cast it into the appropriate view. For example:
ObservableCollection<StepViewModel> vmCollection =
stkStepContent.DataContext as ObservableCollection<StepViewModel>;
foreach(StepViewModel vm in vmCollection)
{
vm.Refresh();
}
EDIT: Does the StepViewModel contain a second ObservableCollection with items that you are trying to refresh? If so, you might be able to wireup a PropertyChanged event so that when the internal ObservableCollection gets updated it triggers a PropertyChanged notification on the StepViewModel
Related
On the main window xaml, I have a Frame that will host 2 pages, like this
<Window x:Class="Monitor.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" FontSize="14">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="48"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Frame Content="Frame" Grid.Row="1" Source="/Monitor;component/Views/Pages/GroupPage.xaml"/>
</Grid>
</Window>
On the first page (GroupPage.xaml) xaml code is
<Page.DataContext>
<VM:GroupPageVM />
</Page.DataContext>
<Grid>
<ItemsControl x:Name="GroupsOfItemsControl" ItemsSource="{Binding GroupsOfItems}">
<ItemsControl.Template>
<ControlTemplate>
<WrapPanel Width="{TemplateBinding Width}" Height="{TemplateBinding Height}"
FlowDirection="LeftToRight" IsItemsHost="true">
</WrapPanel>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button HorizontalAlignment="Right" Margin="3"
Content="{Binding Name}"
Width="1.2in" Height=".75in" FontSize="14"
Command="{Binding Path=GroupSelectedCommand }"
CommandParameter= "{Binding}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
The GroupPageVM is the VM for this page. GroupsOfItems is a collection of VM for groups. Each Group VM has a Name where I bind it for the content of a button that represent for that group. So on the screen, I will see a collection of buttons in a WrapPanel. Everything display correctly.
Now the issue come with the command that handle the click on the button, the code expect I put my Command (GroupSelectedCommand) and its execute function inside the VM of the Group, instead in the MainWindow VM.
Can someone explain why?
If it is how it suppose to work, how do I put the command in the MainWindow VM? because without that I cannot access to the Frame that host the pages or any NavigationService to go to page 2 by clicking on one of those buttons.
(My simple goal is clicking on a button that represent a group, and it will navigate to a page to display items in that group)
You have bound GroupsOfItems as the ItemsSource of the ItemsControl. Inside GroupsOfItems you have GroupVM objects. So each item inside the ItemsControl will be holding a GroupVM object. Also the DataTemplate defined will be representing the GroupVM object. Thus the Command will be expected inside GroupVM object.
To access parent DataContext, you can try giving ElementName as shown below,
Command="{Binding DataContext.GroupSelectedCommand, ElementName=GroupsOfItemsControl }"
In the above code the button will try to take the Command from the DataContext of ItemsControl which is the GroupPageVM
I have a problem with binding a button located in a sidebar in my windows phone app. It seems like the buttons binding just dissapears..
Here's my code at the moment
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<sidebar:SidebarControl x:Name="sidebarControl"
HeaderText="WP"
HeaderBackground="YellowGreen"
HeaderForeground="White"
SidebarBackground="{StaticResource PhoneChromeBrush}">
<sidebar:SidebarControl.SidebarContent>
<Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Width="380">
<Button Content="Go to page 2" x:Name="GoToPage2"/>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>
</sidebar:SidebarControl.SidebarContent>
<Grid VerticalAlignment="Top" HorizontalAlignment="Stretch"
Margin="12">
<TextBlock Style="{StaticResource PhoneTextNormalStyle}">Your current view goes here</TextBlock>
</Grid>
</sidebar:SidebarControl>
</Grid>
</Grid>
At the moment I am using a nuget for the sidebar called SidebarWP8. Maybe Calinbrun.Micro doesnt work with this? Or do I have to insert a binding to the VM in a grid?
Here's the method in the ViewModel:
private readonly INavigationService navigationService;
public MainPageViewModel(INavigationService navigationService)
{
this.navigationService = navigationService;
}
public void GoToPage2()
{
navigationService.UriFor<Page2ViewModel>().Navigate();
}
<Button cm:Message.Attach="[Event Click] = [Action GoToPage2()]" />
This should work, because of the other commenter is correct with respect to the default controls... Custom controls require adding some more handling in which can be a pain in the butt but doing it with the short hand above CM will look for that property in the button and process accordingly.
I checked the source for Caliburn Micro: I handles the convention binding by traversing the visual tree to find elements with a name. But it only checks the default Content property for each control.
That means it will only go through the Content or Children elements and not Custom properties like SidebarContent, so named elements will not be found there.
You have to wire it up by hand (by binding a command or adding a click handler).
I am try to start learning WPF and i am using Telerik.
So i start with simple ComboBox in this article and this is my control:
<telerik:RadComboBox Height="20" Width="200" ItemsSource="{Binding Source={StaticResource DataSource}, Path=Agency}"></telerik:RadComboBox>
What i am trying to do now is to bind an object but first to declare a resource in the XAML (from the article):
<UserControl.Resources>
<example:AgencyViewModel x:Key="DataSource"/> // AgencyViewModel is a class
</UserControl.Resources>
So my problem is that after UserControl i don't have the option Resources, i try to put it inside my control, so i be happy to understand how this is working in WPF
You have to set the DataContext dependency property on a parent control in relation to your ComboBox. The DataContext is then inherited by all (logical-)children. You can then bind to properties on the object referenced by the DataContext dependency property. You do that by referencing the x:Key of your resource with a StaticResource Markup Extension construct.
<UserControl>
<UserControl.Resources>
<example:AgencyViewModel x:Key="DataSource"/> // AgencyViewModel is a class
</UserControl.Resources>
<Grid DataContext="{StaticResource DataSource}">
<telerik:RadComboBox Height="20" Width="200"
ItemsSource="{Binding ItemsCollectionDefinedInViewModel}" />
</Grid>
</UserControl>
You can also do it as it is done in the article without setting the DataContext but instead setting the Source of the binding explicilty.
ItemsSource="{Binding Source={StaticResource DataSource}, Path=Agency}"
I have a WPF Custom Control that I use to display images.
There is a list view which is bound to an observable collection of database entities, which is subsequently converted into an image by virtue of a value converter.
When I drag the control onto a windows forms project (using a WPF Host control) it works perfectly when assigning the observable collection behind a list. I have tested this and it updates correctly and does everything i need it to.
HOWEVER
I would like to have three such controls displaying related images so I created a second control which simply grouped the three original controls into a stack panel.
I created a method for each that updates the images property.
public void ChangeSearchResults(List<ItemImage> items)
{
SearchResultsImageViewer.ItemImages = new ObservableCollection<ItemImage>(items);
}
However I simply cant get the images to show.
There seems to be a difference between viewing a control directly and viewing a control as a child control.
I am pretty sure it is not the element host in winforms as the control works well by itself.
Is there something I am not realising?
This is the Xaml for the list view
<!-- Sets the template for the data to be displayed -->
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel>
<!-- Defines the actual image being displayed -->
<Image x:Name="ItemImageControl"
Width="100"
Height="200"
Margin="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Cursor="Hand"
Source="{Binding .,
Converter={StaticResource imageConverter}}" />
<TextBlock HorizontalAlignment="Center"
FontWeight="Bold"
Text="{Binding .,
Converter={StaticResource groupNameConverter}}" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
EDIT - this is the user control XAML
<UserControl x:Class="Project.CustomControls.ctrlImageCollection"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:CustomControls="clr-namespace:Project.CustomControls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<StackPanel>
<CustomControls:ctrlImageViewer x:Name="ShortlistImageViewer" />
<CustomControls:ctrlImageViewer x:Name="SearchResultsImageViewer" />
<CustomControls:ctrlImageViewer x:Name="GroupImageViewer" />
</StackPanel>
</UserControl>
With using
...
Source={Binding ., Converter={StaticResource imageConverter}}" />
you bind to the current UserControl. When you use your ListView alone, it is bound to the UserControl containing the ListView and will work.
If you put your ListView UserControl into another UserControl its values get bound to its parent UserControl, not longer "own" UserControl.
Try this:
Go to your ListView UserControl xaml.cs and set the DataContext to itself.
//...
DataContext = this;
//...
Hey I was hoping someone could answer a couple questions for me. How am I supposed to ensure that the bound data to itemsource updates dynamically? I can't change bindingsource from staticresource to dynamic resource because the Source Property of the Object Binding is not a dependency property of a dependency object.
What does binding to a staticresource mean exactly? I would think that binding to a dynamicresource would mean that the dependencyproperty updates when the resource changes.
Does binding to a static resource merely attach the resource's initial value?
My goal is just to have signal_viewer update based on the signal_data.
<UserControl.Resources>
<wpfExp:SignalData x:Key="signal_data" />
</UserControl.Resources>
<DockPanel x:Name ="maindockpanel" Height ="Auto" Width ="Auto" LastChildFill="True">
<ToolBarTray DockPanel.Dock="Top">
<ToolBar HorizontalAlignment="Stretch" VerticalAlignment="Top">
<Button Name="load_button" Height="20" Width="Auto" Click="Load_Button_Click">Load</Button>
<Button Name="zoom_in_button" Click="zoom_in_button_Click">Zoom In</Button>
<Button Name="zoom_out_button" Click="zoom_out_button_Click">Zoom Out</Button>
</ToolBar>
</ToolBarTray>
<ItemsControl x:Name ="Signalviewer_Control" ItemsSource="{Binding Source = {StaticResource signal_data}, Path = list_of_signals}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<wpfExp:SignalViewer Signal="{Binding}" MainText="{Binding Path = SignalName}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I'm all against putting the ViewModel or data as a Resource in XAML due to all these issues you mentioned.
Instead, either assign the DataContext in code behind:
public SomeWindow() //Window Constructor
{
DataContext = new SomeViewModel();
}
or use ViewModelLocator
or use the RegisterDataTemplate approach outlined here.
Anyways, if you want to resolve this quickly, change your list_of_signals from List<T> to an ObservableCollection<T>