I'm working on a WPF application which must handle multiple screens (two at this this time).
One view can be opened on several screens and user actions must be reflected consistently on all screens.
To achieve this, for a given type of view, a single DataContext is instantiated. Then, when a view is displayed on a screen, the unique DataContext is attached to it. So, one DataContext, several views (same type of view/xaml).
So far so good. It works quite well in most cases.
I do have a problem with a specific view which relies on ItemsControl. These ItemsControl are used to display UIElements dynamically build in the ViewModel/DataContext (C# code). These UIElements are mostly Path objects. Example :
<ItemsControl ItemsSource="{Binding WindVectors}">
<ItemsControl.Template>
<ControlTemplate TargetType="{x:Type ItemsControl}">
<Canvas IsItemsHost="True" />
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
Here, WindVectors is a ObservableCollection<UIElement>.
When the view is opened the first time, everything is fine. The problem is that when the view is opened one another screen, all ItemsControl are removed from the first screen and displayed one the second screen. Other WPF components (TextBlock for instance) on this view react normally and are displayed on both screens.
Any help would be greatly appreciated.
Thanks.
Fabrice
This is the expected behavior (ie been that way since winforms)- this is because the ObservableCollection is a reference. This wont happen with value types, only reference types.
The short answer is 'dont do that'. You could try looking into defining a collection view in the xaml or code a custom data provider and bind to one of those instead.
Related
I'm using an MVVM pattern for my WPF application. If the "home" view model, which controls the layout of my application's main window, I have a ChildViewModel property. This holds a viewmodel that can be switched according to what the user is doing. When they select menu items, the child view model switches and the main area of the screen (it's in an Outlook style) switches accordingly.
I do this with a ContentControl and DataTemplate like this: (I'm only showing one of the embeddable views here to keep it short).
<ContentControl Grid.Row="1" Grid.Column="1" Margin="3"
Content="{Binding ChildViewModel}">
<ContentControl.Resources>
<DataTemplate DataType="{x:Type vm:VersionsViewModel}">
<Embeddable:VersionsView />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
I also want to add a ribbon to my main window, using the Telerik RadRibbonView control. I want this to have some fixed tabs and buttons that are always visible. In addition, I want to add and remove entire tabs, and buttons within existing tabs, according to the type of child view model. I'd like this to be done in the view in a similar manner to the way I've done the content control, above.
Is this possible? I've tried lots of things but got nowhere so far. I know I could do it by creating a huge "super ribbon" and binding visibility properties but this seems cludgey. I could also have multiple ribbons, each containing the common controls, but this would cause a maintenance problem.
In the end I went with the "super ribbon" approach, as I couldn't find any other way.
I am new to WPF. I'm trying to build an application which has a function (call it Initialisation) where a user has to fill in a lot of data and some parts of the form are repeated. We're rewriting a legacy app that has quite a long wizard in although we will probably use collapsible panels in one window rather than next/previous pages. Also some parts are repeated e.g. the user can specify a number of items, if they say 3 they will need to fill in some configuration info for each, so those controls would need to be repeated three times.
I'm using MVVM and am using this example here: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
The old wizard had about 4 pages so I'm intending to have one user control (Initialisation) that contains 4 child user controls to break the xaml up a bit.
So far I have the Initialisation (its ViewModel inherits from Workspace ViewModel as in the above example) and it contains one child which is working:
<Expander ExpandDirection="Down" Header="ChildOne">
<view:ChildOne />
</Expander>
I will have separate ViewModels for each child and for Intialisation and this brings me to my problem.
The problem I am having is that ChildOne contains a dropdown which I am trying to bind like so:
<ComboBox x:Name="textMessageTypeCmb" ItemsSource="{Binding Path=TextMessageSelectionOptions, Mode=OneTime}"/>
TextMessageSelectionOptions is a public property in ChildOne's ViewModel. This results in no errors but an empty dropdown - that property getter is never called. If I move that property getter code into the Initialisation's ViewModel instead it works but I'm trying to keep my code in manageable chunks so I'd like to put hat code back in ChildOne's ViewModel. It also works if in my MainWindow I create ChildOne as a workspace instead of Initialisation like this
ChildOneViewModel ws = this.Workspaces.FirstOrDefault(vm => vm is ChildOneViewModel) as ChildOneViewModel;
Can anyone advise whether I am taking the right approach (by dividing it up into several user controls) and what I need to do in the binding to make this work? I don't really understand any of this yet especially binding.
It seems to me that your ChildOne view's DataContext is still this Initialisation vm.
You can bind it the views Datacontext to a ChildOneViewModel object
...
<view:ChildOne DataContext={Binding PropertyReturnsChildOneViewModellObject/>
...
or specify the path for the combobox ItemsSource prop.
<ComboBox x:Name="textMessageTypeCmb" ItemsSource="{Binding Path=PropertyReturnsChildOneViewModellObject.TextMessageSelectionOptions, Mode=OneTime}"/>
Note: PropertyReturnsChildOneViewModellObject is a property of the Initialisation vm.
In the above image, child is a ContentPresenter. Its Content is a ViewModel. However, its ContentTemplate is null.
In my XAML, I have a TabControl with the following structure:
<local:SuperTabControlEx DataContext="{Binding WorkSpaceListViewModel}"
x:Name="superTabControl1" CloseButtonVisibility="Visible" TabStyle="OneNote2007" ClipToBounds="False" ContentInnerBorderBrush="Red" FontSize="24" >
<local:SuperTabControlEx.ItemsSource>
<Binding Path="WorkSpaceViewModels" />
</local:SuperTabControlEx.ItemsSource>
<TabControl.Template>
<ControlTemplate
TargetType="TabControl">
<DockPanel>
<TabPanel
DockPanel.Dock="Top"
IsItemsHost="True" />
<Grid
DockPanel.Dock="Bottom"
x:Name="PART_ItemsHolder" />
</DockPanel>
<!-- no content presenter -->
</ControlTemplate>
</TabControl.Template>
<TabControl.Resources>
<DataTemplate DataType="{x:Type vm:WorkSpaceViewModel}">
....
WorkSpaceViewModels is an ObservableCollection of WorkSpaceViewModel. This code uses the code and technique from Keeping the WPF Tab Control from destroying its children.
The correct DataTemplate - shown above in the TabControl.Resource - appears to be rendering my ViewModel for two Tabs.
However, my basic question is, how is my view getting hooked up to my WorkSpaceViewModel, yet, the ContentTemplate on the ContentPresenter is null? My requirement is to access a visual component from the ViewModel because a setting for the view is becoming unbound from its property in the ViewModel upon certain user actions, and I need to rebind it.
The DataTemplate is "implicitly" defined. The ContentPresenter will first use it's ContentTemplate/Selector, if any is defined. If not, then it will search for a DataTemplate resource without an explicit x:Key and whose DataType matches the type of it's Content.
This is discussed here and here.
The View Model shouldn't really know about it's associated View. It sounds like there is something wrong with your Bindings, as in general you should not have to "rebind" them. Either way, an attached behavior would be a good way to accomplish that.
I think the full answer to this question entails DrWPF's full series ItemsControl: A to Z. However, I believe the gist lies in where the visual elements get stored when a DataTemplate is "inflated" to display the data item it has been linked to by the framework.
In the section Introduction to Control Templates of "ItemsControl: 'L' is for Lookless", DrWPF explains that "We’ve already learned that a DataTemplate is used to declare the visual representation of a data item that appears within an application’s logical tree. In ‘P’ is for Panel, we learned that an ItemsPanelTemplate is used to declare the items host used within an ItemsControl."
For my issue, I still have not successfully navigated the visual tree in order to get a reference to my splitter item. This is my best attempt so far:
// w1 is a Window
SuperTabControlEx stc = w1.FindName("superTabControl1") as SuperTabControlEx;
//SuperTabItem sti = (SuperTabItem)(stc.ItemContainerGenerator.ContainerFromItem(stc.Items.CurrentItem));
ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(stc);
//ContentPresenter myContentPresenter = FindVisualChild<ContentPresenter>(sti);
DataTemplate myDataTemplate = myContentPresenter.ContentTemplate;
The above code is an attempt to implement the techniques shown on the msdn web site. However, when I apply it to my code, everything looks good, except myDataTemplate comes back null. As you can see, I attempted the same technique on SuperTabControlEx and SuperTabItem, derived from TabControl and TabItem, respectively. As described in my original post, and evident in the XAML snippet, the SuperTabControlEx also implements code from Keeping the WPF Tab Control from destroying its children.
At this point, perhaps more than anything else, I think this is an exercise in navigating the Visual Tree. I am going to modify the title of the question to reflect my new conceptions of the issue.
I have a WPF application with two pages. On page one, there is a TextBox (boxSource). On page two, I have a TextBlock (blockDestination). I want to databind in XAML, the Text property of boxSource to the Text property of blockDestination.
I set the DataContext of page two to page one when the application is initialized. I setup blockDestination as follows:
<TextBlock Name="blockDestination" Grid.Row="0" Grid.Column="1" Text="{Binding boxSource, Path=Text, Mode=OneWay}" />
This does not pickup the value of the TextBox. My guess is that it is because the TextBox is a variable instead of a property?
Can anyone explain the issue, and is there an elegant solution?
Thanks for any help
For that XAML to work, your "page one" will need to be set as the data context of page two, with the boxSource variable defined as a property, so that in the setter, you can raise the PropertyChanged event.
Matthias is right, though, this is a pretty brittle way to implement this, and one of the places where an MVVM approach will be more robust in the long run.
An elegant solution would be to define a View Model common to all pages. You should always bind to properties of the view model and should avoid binding to UI Elements. Using a view model you can always access all neccessary values and define several presentations in different pages.
I've read something about pages having their own object space, so that UI elements can have the same name in different pages. Also it can happen that the first page isn't available after loading the second one. The binding target would then be unavailable.
I'm brand spanking new to WPF and am trying to play around with projects to better understand what I'm reading.
My understanding of a resource is that it is the instance, you can't use it like a factory and create instances of it. For example, a XAML-defined rectangle. You can reference it, but you can't have numerous instances of it all over the surface.
In WPF, what would be the way to do that? If I define a Rectangle as a resource with specific properties and wanted to have multiple instances of that within a dynamically-generated grid, how should I be going about it? Or is there a different way I should be trying to do this?
Purely academic exercise with no real-world application.
Actually there's nothing about resources in particular that prevents you from using it multiple times. A perfect example of this is brush resources, style resources, etc. You define them in XAML and the XAML parser creates a single instance of the resources and stores them in the resource dictionary and these brushes, styles, etc can be used as property values many times even though only a single instance of the resource was created.
But having said that, as you noted, you can't really define a Rectangle resource and use it multiple times in the visual tree. This has nothing to do with the fact that it's a resource, but rather it has to do with the fact that a FrameworkElement cannot be a child of more than one parent element.
So what we have instead is called "templates". These tell WPF how to create an element tree but does not actually create the tree until you instantiate the template. Below is an example.
<UserControl>
<ItemsControl ItemsSource="{Binding WholeBunchOfItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Fill="Yellow" />
<ContentPresenter Content="{Binding}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
In this example I've bound an ItemsControl to a collection of some sort. For each item in the collection, the ItemsControl will use my DataTemplate to render the item. Within a DataTemplate you can use data binding to access the current item.
I would suggest reading up on MSDN about ControlTemplate, DataTemplate, and Style. These are all important concepts in WPF/Silverlight.
To get multiple instances replicated across a grid or listbox, you need to set the data template to define the UI controls for each row of data, and then databind the grid or listbox to a collection of data that determines how many rows and the individual field values.
Key term for you to research first: data template.