Settings Window design - binding Page to TreeViewItem - c#

I'm just starting with WPF (coming from WinForms) and I want to make settings window like the one in Visual Studio. Category tree on left side and appropriate views on the right.
In WinForms I did it by adding to TreeViewItem's TAG name of the window that should be displayed and then in OnClick I was creating that window using reflection. Something like this:
//pseudocode
TreeViewItem item = CreateTreeViewItem();
item.Tag = "GeneralSettingsWindow";
item.Text = "General settings";
------------------------------------------------------------------------
void ItemClick(object sender)
{
TreeViewItem item = sender as TreeViewItem;
string formName = item.Tag.ToString();
BaseSettingsForm f = Activator.CreateInstance(formName);
settingsPanel.Controls.Clear();
settingsPanel.Controls.Add(f);
}
And it worked fine.
I'm curious how can I achieve that functionality using WPF binding. Is it possible anyway?

I have done a tree view in left side and when you click an item it will update the contents of right side panel. In order to do that you have to make two panels or grid in an window, one in left side with less width and another in right side. Then put the tree view in the left side and update the contents of right side grid by adding different types of user controls or by showing and hiding different grids with the corresponding click of tree view item.
Here is an example of making the tree view and you can modify that according to your own requirement.
<TreeView MouseLeftButtonUp="Get_MFR" SelectedItemChanged="treeviewQuestion_SelectedItemChanged"
x:Name="treeviewQuestion" FontFamily="Nunito" FontSize="16" Padding="10 10"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
BorderThickness="0" Margin="0,0,0,27"
>
<TreeView.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="10 2">
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type TreeViewItem}},Path=Tag,
Converter={x:Static local:TagToTextConverter.Instance}}" />
<TextBlock Text="{Binding}"/>
<Rectangle Margin="5" Width="10" Height="10" Fill="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type TreeViewItem}},Path=Tag,
Converter={x:Static local:TagToColorConverter.Instance}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.Resources>
</TreeView>

Even though you're new to WPF development, I'd still suggest you take a look at the MVVM design pattern. The separation between user interface and business logic makes this type of application structure much more straightforward.
Replace your settings panel with a ContentControl, and define as many DataTemplates as you require for different tree node types. You can then bind its Content property to the selected TreeViewItem.
Unfortunately, "out of the box" WPF doesn't support binding to the selected item in a TreeView. However, there are several ways around that, including using a Behaviour, which I described in a recent blog post.

Related

Prism WPF Dynamic Regions

Let's say we have a Prism 7 application with modules A and B. The main window has a Tab Control and two buttons, which add either module A or B to the Tab Control. I created a binding for the Tab Control items and implemented an item template, which includes Prism Region, whose name is bound to the item name.
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl prism:RegionManager.RegionName="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
The problem I am facing is that the region name doesn't seem to change, i.e. if I first add Module A, all the next button clicks will add Module A and vice versa. In my previous question Prism WPF Binding RegionManager.RegionName I was explained that I shouldn't bind the RegionManger.RegionName, so my question is, how should I implement the requirements?
Link to the repo: https://github.com/moisejbraver/ModulesTest
This is the way i handle tab controls with prism regions, i think this is helpful for you too.
<TabControl prism:RegionManager.RegionName="{x:Static local:RegionNames.AdvancedSetup}">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Visibility" Value="{Binding DataContext.IsAvailable, Converter={coverters:BooleanToVisibilityConverter}}"/>
<Setter Property="Header" Value="{Binding DataContext.Name}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
Then i register all my tabs to the same region in OnInitialized function of each module.
regionManager.RegisterViewWithRegion(RegionNames.AdvancedSetup, setupType);
I've made an interface which all viewmodels of tabs will implement,
it contains
Name of the tab
Whether the tab is visible.
You may need to change some details based on your need.

WPF Content View not displaying DataTemplate

I have a ContentControl which will not display any XAML from its DataTemplate, and I feel certain that the problem I'm facing will be obvious for those with better WPF codemancy than myself. I have substituted "Object" for my object name where appropriate for confidentiality reasons.
In my MainWindow.xaml I have this:
<ContentControl x:Name="ObjectDetailView"
Margin="20,20,20,20" Grid.Row="2" Grid.Column="1"
DataContext="{Binding SelectedItem, ElementName=ObjectListView}"
Template="{DynamicResource DetailControlTemplate}"
ContentTemplate="{DynamicResource DetailDataTemplate}"/>
I keep my templates in separate files to keep code readable. The template is in a DataResources.xaml file that is being successfully used for the ListView. The code for the content/template in question is:
<ControlTemplate x:Key="DetailControlTemplate">
<Border Style="{StaticResource ObjectDetailBorderStyle}">
<ContentPresenter/>
</Border>
</ControlTemplate>
<DataTemplate x:Key="DetailDataTemplate" DataType="{x:Type model:Object}">
<!-- Valid XAML -->
</DataTemplate>
In my Designer (both in VS and Blend) The border/background gradient displays, but nothing further. Same for the running program.
If I move the <!-- Valid XAML --> into the Control Template, it displays fine, but I don't believe that's kosher, and I also don't believe that the data-binding will work that way. Please correct me if I'm wrong.
ObjectListView is a ListView populated dynamically from my VM, and I'm using MVVM. That all works just fine. I'd prefer this ContentControl only appears once there is a valid selected object in the list view, but that's UX sugar at this point, thus my only concern is to get this content control displaying my model's data.
I'm also fairly new to StackOverflow, so if I missed anything or made an error in posting this question, please let me know. I've not had luck with searching for this issue over the last few hours, as I don't want to waste your time.
Two things. You did not set the actual Content of the ContentControl, but only its DataContext. You should instead write this:
<ContentControl ...
Content="{Binding SelectedItem, ElementName=ObjectListView}"
Template="{DynamicResource DetailControlTemplate}"
ContentTemplate="{DynamicResource DetailDataTemplate}"/>
And your ControlTemplate is missing a TargetType:
<ControlTemplate x:Key="DetailControlTemplate" TargetType="ContentControl">
<Border Style="{StaticResource ObjectDetailBorderStyle}">
<ContentPresenter/>
</Border>
</ControlTemplate>
Without the TargetType, the ContentPresenter's properties aren't set by default, and you would have to set them explicitly like
<ControlTemplate x:Key="DetailControlTemplate">
<Border Style="{StaticResource ObjectDetailBorderStyle}">
<ContentPresenter
Content="{Binding Content,
RelativeSource={RelativeSource TemplatedParent}}"
ContentTemplate="{Binding ContentTemplate,
RelativeSource={RelativeSource TemplatedParent}}"/>
</Border>
</ControlTemplate>

WPF DataGrid: IsVirtualizingWhenGrouping="True" not working

I have a DataGrid that has a CollectionViewSource bound to it's ItemSource Property:
<DataGrid Grid.Row="0" RowBackground="#10808080" AlternatingRowBackground="Transparent"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding Source={StaticResource bookingsViewSource}}"
RowHeight="27"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
VirtualizingPanel.IsContainerVirtualizable="True"
VirtualizingPanel.ScrollUnit="Item"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding date, StringFormat=dd.MM.yyyy}" Header="date"/>
<DataGridTextColumn Binding="{Binding Path=customers.name}" Header="customer"/>
<DataGridTextColumn Binding="{Binding Path=customers.street}" Header="adress"/>
</DataGrid.Columns>
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander Header="{Binding Path=Name}" IsExpanded="True">
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
bookingsViewSource is defined as
<CollectionViewSource x:Key="bookingsViewSource"
d:DesignSource="{d:DesignInstance {x:Type Database:bookings}}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="providerID"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
and get's filled in code behind section. Everything was doing fine fast and smooth without grouping. But when I added grouping <PropertyGroupDescription PropertyName="providerID"/> the DataGrid needs around one minute to load.
In .NET 4.5 there is a new Property called VirtualizingPanel.IsVirtualizingWhenGrouping and I already set this to true but loading time was not decreasing.
I can not figure out why. Any ideas?
From the book: MacDonald M. - Pro WPF 4.5 in C#
A number of factors can break UI virtualization, sometimes when you don’t expect it:
Putting your list control in a ScrollViewer: The ScrollViewer provides a window onto
its child content. The problem is that the child content is given unlimited “virtual”
space. In this virtual space, the ListBox renders itself at full size, with all of its child
items on display. As a side effect, each item gets its own memory-hogging
ListBoxItem object. This problem occurs any time you place a ListBox in a container
that doesn’t attempt to constrain its size; for example, the same problem crops up if
you pop it into a StackPanel instead of a Grid.
Changing the list’s control template and failing to use the ItemsPresenter: The
ItemsPresenter uses the ItemsPanelTemplate, which specifies the
VirtualizingStackPanel. If you break this relationship or if you change the
ItemsPanelTemplate yourself so it doesn’t use a VirtualizingStackPanel, you’ll lose
the virtualization feature.
Not using data binding: It should be obvious, but if you fill a list programmatically—
for example, by dynamically creating the ListBoxItem objects you need—no
virtualization will occur. Of course, you can consider using your own optimization
strategy, such as creating just those objects that you need and only creating them at
the time they’re needed. You’ll see this technique in action with a TreeView that uses
just-in-time node creation to fill a directory tree in Chapter 22.
If you have a large list, you need to avoid these practices to ensure good performance.
Also, in my case the issue was caused by MahApps.Metro styles.

VisualBrush (linked to VLC) doesn't change on TabControl selection change

I am having a weird experience with a combination of visual brush (linked to a VLC player through VLC.DotNet) and a tab control. I have created a custom control using the VLC player to watch an RTSP stream and have multiple of these controls in a given window.
The problem is that if I put all the controls in a list view they all display properly. But if I put them in a tab control then it always shows the stream of the first-selected tab item, not matter what tab I'm currently on. Everything else in the control (label, etc.) changes properly, but not the part drawn by the visual brush.
The view for my control is defined as:
<UserControl x:Class="myApp.View.CameraMonitorView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Wpf="clr-namespace:Vlc.DotNet.Wpf;assembly=Vlc.DotNet.Wpf"
Loaded="UserControl_Loaded">
<DockPanel>
<Label Content="{Binding Name}" DockPanel.Dock="Bottom"/>
<Grid Margin="3">
<Grid.Background>
<VisualBrush Stretch="Uniform">
<VisualBrush.Visual>
<Image Source="{Binding VideoSource, ElementName=vlcControl}"/>
<!--<Image Source="{Binding Image}" /> -->
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
<Wpf:VlcControl x:Name="vlcControl" Margin="3"/>
</Grid>
</DockPanel>
</UserControl>
The code behind for the view starts playing the RTSP, but I don't think that code will help with this problem.
Meanwhile the ViewModel (stripped down for ease of viewing) is just:
class CameraMonitorViewModel : ViewModelBase
{
public CameraMonitorViewModel(string name, string image)
{
Name = name;
Image = image;
}
public string Name {get; set;}
public string Image { get; set; }
}
And I have a data template defined as:
<DataTemplate DataType="{x:Type vm:CameraMonitorViewModel}">
<v:CameraMonitorView HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</DataTemplate>
The full window view model has an ObservableCollection called Monitors and the view displays:
<TabControl ItemsSource="{Binding Monitors}" SelectedIndex="0" Height="300">
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Content" Value="{Binding}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
<ListView ItemsSource="{Binding Monitors}" Height="300" />
The ListView properly shows different images from each camera. The tab item will always show the same camera, but the control's label item changes when I clicked different tabs. Moreover, if I replace the databinding to the VLC control's image to the commented out static image object (whose source is set by way of the view model) then the image will properly change when I click different tabs.
I'm really confused and would appreciate any help that could be provided.
Yeah, as I noted in the comment to the question the problem was that I was starting the stream playing based on the Loaded event for the control. But it seems like it doesn't get fired after the second tab, probably because of what Rachel mentions here: Loaded event doesn't fire for the 4th TabControl tab (being that it reuses the template rather than loading multiple ones).

Setting Canvas Children Property without ItemsControl ItemsSource Binding Property

Is there any means to set Canvas Children Property without ItemsControl ItemsSource Binding Property?
In order to keep separated my view from viewmodel, I have to bind the items.
I have used the the canvas as a designer from 'CodeProject'
http://www.codeproject.com/KB/WPF/WPFDiagramDesigner_Part2.aspx
I'm using a canvas for drag-and-drop purposes. It works well when I work manually inside the canvas.
Which means I add and remove the child items using
myCanvas.Children.Add(userControl);
myCanvas.Children.Remove(userControl);
But if I load my usercontrols at run time, they are loaded just as views.
<s:Canvas AllowDrop="True" >
<ItemsControl Grid.Row="1" ItemsSource="{Binding Path=userControls}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<s:Canvas Background="Transparent"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<s:ControlItem Content="{Binding Path=MyView}"></s:ControlItem >
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left" Value="{Binding Path=X}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
</s:Canvas>
No, there aint. (Except manually clearing and adding...)
Ummm yeah just draw items inside the canvas? :)
<Canvas>
<TextBlock Text="I'm Child #1" />
<TextBlock Text="I'm Child #2" Canvas.Top="50" />
</Canvas>
Or you can always do it in code-behind
myCanvas.Children.Add(myTextBlock);
foreach(var someControl in SomeControlList)
myCanvas.Children.Add(someControl);
Edit
I see your update and have no idea what you're asking. If you want to drag/drop items onto a Canvas, you are better off adding/removing items from the ItemsSource than manually adding/removing items from the Canvas. Simply adding/removing them from myCanvas will not update the collection in your ItemsSource
I would recommend taking a look at Bea Stollnitz's article on dragging/dropping databound Items. This means you would keep the ItemsControl you have now, but when you drop an item on top of the Canvas it adds the DataItem behind that object to the ObservableCollection<MyDataItem> that you call userControls (I don't like this name because it suggests that the data items contain UI items, which should not be the case)

Categories