SimpleIoc MVVM resolve view (UserControl) - wpf

Hope someone can enlighten me on the best mvvm practice using service locator.
Base principles are clear. I have my views with corresponding view model, everything works at it should.
Lets make a simple example. I have one main window and 2 user controls UCA and UCB. Each of them have view model registered in the locator class.
Using this IoC pattern how can you display UCA and UCB in the main window using one content control and binding through main window view model? To be precise I want to show only one control at the same time. I can't bind the UCA or UCB view model because this is view first approach, the view is not automatically resolved.
What is the correct approach for this?
Thanks

You can create a separate service for launching views as dialog, so that it can be used in a generic way across the application. And will inject this service to the ViewModel via Constructor which wants to launch any dialog.
public interface IDialogService<T>
{
void Show();
void ShowDialog();
}
public class DialogService<T> : IDialogService<T> where T : Window
{
public void Show()
{
container.Resolve<T>().Show();
}
public void ShowDialog()
{
container.Resolve<T>().ShowDialog();
}
}
Now just inject this service to the respective viewmodel.
public class YourViewModel
{
//commands
public ICommand someCommand { get; set; }
private IDialogService<BookingView> _dialogService;
public YourViewModel(IDialogService<YourView > dialogService)
{
_dialogService = dialogService
someCommand = new RelayCommand(someCommandDoJob, () => true);
}
public void someCommandDoJob(object obj)
{
//Since you want to launch this view as dialog you can set its datacontext in its own constructor.
_dialogService.ShowDialog();
}
}

One solution could be using ContentTemplate property together with DataTrigger to selectively show UCA or UCB (see example below). UCA could also be set as default view in a style setter. In this case only one DataTriggers is needed.
Another solution could be using ContentTemplateSelector property and implement a DataTemplateSelector.
See: ContentControl.ContentTemplateSelector (MSDN)
<!--content control in main window-->
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<!--suppose main window view model has a bool property UseUCA-->
<!--if UseUCA is true render UCA view-->
<DataTrigger Binding="{Binding UseUCA}" Value="True">
<DataTrigger.Setters>
<!--by setting ContentTemplate property you control what is rendered inside of the content control-->
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!--render UCA view-->
<local:UCA />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger.Setters>
</DataTrigger>
<!--if UseUCA is false render UCB view-->
<DataTrigger Binding="{Binding UseUCA}" Value="False">
<DataTrigger.Setters>
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<!--render UCB view-->
<local:UCB />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger.Setters>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

OK... here is a thought... you can keep track of the "currentview" in your MainViewModel and let the UI show the correct control based on type. Here is a quick example. I am using a Button to switch views... this could ideally be done via any logic that fits your requirements.
MainWindow:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication4"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding Main, Source={StaticResource Locator}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Switch Views" Command="{Binding SwitchViewsCommand}" />
<ContentControl Grid.Row="1" Content="{Binding CurrentControl}">
</ContentControl>
</Grid>
</Window>
UCA and UCB are simply user controls with different text in them:
<UserControl x:Class="WpfApplication4.UserControls.UCA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApplication4.UserControls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<TextBlock Text="This is user control A" />
</Grid>
</UserControl>
UCAViewModel and UCBViewModel are empty viewmodels in my example that inherit from ViewModelBase
namespace WpfApplication4.ViewModel
{
public class UCAViewModel : ViewModelBase
{
}
}
The MainViewModel handles the currently shown view based on its viewmodel
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Command;
using Microsoft.Practices.ServiceLocation;
namespace WpfApplication4.ViewModel
{
public class MainViewModel : ViewModelBase
{
public MainViewModel()
{
RegisterCommands();
SwitchView();
}
private void SwitchView()
{
if(CurrentControl == null)
{
CurrentControl = ServiceLocator.Current.GetInstance<UCAViewModel>();
}
else
{
if(CurrentControl is UCAViewModel)
CurrentControl = ServiceLocator.Current.GetInstance<UCBViewModel>();
else
CurrentControl = ServiceLocator.Current.GetInstance<UCAViewModel>();
}
}
private ViewModelBase _currentControl;
public ViewModelBase CurrentControl
{
get { return _currentControl; }
set
{
if (_currentControl != value)
{
_currentControl = value;
RaisePropertyChanged("CurrentControl");
}
}
}
private void RegisterCommands()
{
SwitchViewsCommand = new RelayCommand(SwitchView);
}
public RelayCommand SwitchViewsCommand { get; private set; }
}
}
the ViewModelLocator stores the instances
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Ioc;
using Microsoft.Practices.ServiceLocation;
namespace WpfApplication4.ViewModel
{
/// <summary>
/// This class contains static references to all the view models in the
/// application and provides an entry point for the bindings.
/// </summary>
public class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<MainViewModel>();
SimpleIoc.Default.Register<UCAViewModel>();
SimpleIoc.Default.Register<UCBViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
}
And finally, the glue that makes the correct view show is done in the app.xaml file:
<Application x:Class="WpfApplication4.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApplication4" StartupUri="MainWindow.xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:localUC="clr-namespace:WpfApplication4.UserControls"
xmlns:vm="clr-namespace:WpfApplication4.ViewModel">
<Application.Resources>
<vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" />
<DataTemplate DataType="{x:Type vm:UCAViewModel}">
<localUC:UCA />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:UCBViewModel}">
<localUC:UCB />
</DataTemplate>
</Application.Resources>
</Application>

Related

WPF - How to handle event raised from UserControl in MainWindow and update ViewModel

I am trying to raise an event in UserControl1 but I could not figure out how to handle that event to display UserControl2 in MainWindow.
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel1 />
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="Template1" DataType="{x:Type local:ViewModel1}">
<local:UserControl1 />
</DataTemplate>
<DataTemplate x:Key="Template2" DataType="{x:Type local:ViewModel1}">
<local:UserControl2 />
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{Binding }">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate" Value="{StaticResource Template1}" />
<Style.Triggers>
<DataTrigger Binding="{Binding UserControlId}" Value="2">
<Setter Property="ContentTemplate" Value="{StaticResource Template2}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Grid>
</Window>
MainWindow.cs
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
UserControl1.xaml
<UserControl x:Class="WpfApp1.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Label Content="User Control 1" />
<Button x:Name="btnNext" Content="Next" Click="btnNext_Click"></Button>
</Grid>
</UserControl>
UserControl1.cs
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public partial class UserControl1 : UserControl
{
public static readonly RoutedEvent MyEvent = EventManager.RegisterRoutedEvent(
"MyEventName",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(UserControl1)
);
public event RoutedEventHandler LoginEventHandler
{
add { AddHandler(MyEvent, value); }
remove { RemoveHandler(MyEvent, value); }
}
public UserControl1()
{
InitializeComponent();
}
private void btnNext_Click(object sender, RoutedEventArgs e)
{
var eventArgs = new RoutedEventArgs(MyEvent);
RaiseEvent(eventArgs);
}
}
}
UserControl2.xaml
<UserControl x:Class="WpfApp1.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Label Content="User Control 2" />
</Grid>
</UserControl>
UserControl2.cs
using System.Windows.Controls;
namespace WpfApp1
{
public partial class UserControl2 : UserControl
{
public UserControl2()
{
InitializeComponent();
}
}
}
ViewModel1.cs
using System.ComponentModel;
namespace WpfApp1
{
public class ViewModel1 : INotifyPropertyChanged
{
public int UserControlId { get; set; }
public ViewModel1()
{
UserControlId = 2;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
I think i have to update the UserControlId property in ViewModel1 to change the user control but where to handle and how to get the ViewModel1 instance to change the property?
EDIT 1
Sorry for the typo in the ViewModel1 constructor, i meant to initialize the UserControlId property to 1, not 2.
When i run this, i see the window with UserControl1 loaded in the main window.
UserControl1 is having a button "Next".
On clicking the button in UserControl1, i want to show UserControl2.
So I am raising MyEvent routed event inside button click event handler.
Now, where to attach and write the event handler for MyEvent and how to get the ViewModel1 instance to change it's UserControlId property?
I finally got this to work. Raised event changed with property name in ViewModel1, added event listener in MainWindow,
Here are the changed files
ViewModel1.cs
using System.ComponentModel;
namespace WpfApp1
{
public class ViewModel1 : INotifyPropertyChanged
{
private int _userControlId;
public int UserControlId {
get { return _userControlId; }
set
{
_userControlId = value;
RaisePropertyChanged("UserControlId");
}
}
private void RaisePropertyChanged(string v = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(v));
}
public ViewModel1()
{
UserControlId = 1;
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
MainWindow.cs
using System.Windows;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
AddHandler(UserControl1.MyEvent, new RoutedEventHandler(OnMyEvent));
}
private void OnMyEvent(object sender, RoutedEventArgs e)
{
var vm = (ViewModel1)DataContext;
vm.UserControlId = "2";
}
}
}
Don't think the routed event is needed here. Assign a ViewModel to MainWindow and add the Property "UserControlId" in the MainViewModel. Then your XAML code would work properly as you have written a trigger on UserControlId's value at the MainWindow's DataContext level.
In order to do this you need something to mediate between the two view models. One way to mediate is for the main window to be the mediator: it listens to events from the children and then routes them to their required destination. Another way is via an event aggregator. Most MVVM frameworks have one. The best open source one I know of in the WPF world is the one in Caliburn Micro. You subscribe to some kind of message in one view model and then send the event from some other view model and the event aggregator ensures that the event gets routed to its destination. See their documentation for more details.

How to deactivate the sorting done using CollectionViewSource.SortDescription?

I am using a CollectionViewSource and have set up the CollectionViewSource.SortDescription property in xaml for sorting the collection on a particular property.
However, now I have a case wherein if certain condition is true, or collection is of particular type, I don't need to apply the sorting, but rather to just bind the collection as it is from the view model.
I don't want to move the sorting out of XAML to view model, since it complicates the matter. I want to leave sorting to CollectionViewSource.SortDescription, however want to know if there is a way to turn it off based on some flag. E.g. I can expose a property IgnoreSort in my view model and just somehow consume it to turn the CollecitonViewSource sorting off.
Following is the xaml code-
Resource:
<UserControl.Resources>
<CollectionViewSource x:Key="PeopleItemsSource" Source="{Binding Department.ActivePeople}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="DisplaySortOrder" />
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</UserControl.Resources>
And here is the control using the resource declared above
<ItemsControl x:Name="peopleitems"
Grid.Row="1"
ItemsSource="{Binding Source={StaticResource PeopleItemsSource}}"/>
Note: Item template is not added here to simplify the xaml.
Here's a small example I wrote, I don't think you'll have issues adapting it to your project, it uses DataTriggers to conditionally choose the itemssource, it doesn't use code behind, the code behind you see here is just to setup the datacontext, the items collection etc. Copy paste this code into a new WPF project and see it working
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"
>
<Window.Resources>
<CollectionViewSource x:Key="colSrc" Source="{Binding MyList}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
<Style TargetType="ItemsControl" x:Key="ic">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSorted}" Value="True">
<Setter Property="ItemsSource" Value="{Binding Source={StaticResource ResourceKey=colSrc}}"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSorted}" Value="False">
<Setter Property="ItemsSource" Value="{Binding MyList}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<CheckBox IsChecked="{Binding IsSorted}"/>
<ItemsControl Grid.Row="1" Style="{StaticResource ic}"/>
</StackPanel>
</Grid>
MainWindow.xaml.cs
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window,INotifyPropertyChanged
{
private bool isSorted;
public bool IsSorted
{
get
{
return isSorted;
}
set
{
isSorted = value;
OnPropertyChanged("IsSorted");
}
}
private ObservableCollection<string> myList;
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> MyList
{
get
{
return myList;
}
set
{
myList = value;
this.OnPropertyChanged("MyList");
}
}
public MainWindow()
{
InitializeComponent();
this.MyList = new ObservableCollection<string>() {
"C",
"B",
"A"
};
this.DataContext = this;
}
private void OnPropertyChanged(string p)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(p));
}
}
}
}
you must do it by code - add or remove the SortProperties
internal void Sort(PropertyModel model)
{
PropertyViewModel prop = SortProperties.Find(p => p.Data == model);
IEnumerable<SortDescription> result =
Books.SortDescriptions.Cast<SortDescription>().Where(p => p.PropertyName == prop.Data.FullName);
if (result != null && result.Count() == 1)
{
Books.SortDescriptions.Remove(result.First());
}
else
{
Books.SortDescriptions.Add(new SortDescription(prop.Data.FullName, ListSortDirection.Ascending));
}
RaisePropertyChanged("Books");
}

Load controls on runtime based on selection

I'm new to XAML and I have a case where I need to change controls based on a selection on a combobox with templates.
For example, let's say that a user selects a template that requires a day of week and a time range that something will be available. I would like that, on the moment of the selection, the control with the information needed get build on the screen and that the bindings get to work as well.
Can someone give me a hint or indicate an article with an elegant way to do so?
Thanks in advance.
The solution you are looking for is a ContentControl and DataTemplates. You use the selected item of the ComboBox to change ContentTemplate of the Content Control.
You question mentions binding so I will assume you understand the MVVM pattern.
As an example, lets use MyModel1 as the Model
public class MyModel1
{
private Collection<string> values;
public Collection<string> Values { get { return values ?? (values = new Collection<string> { "One", "Two" }); } }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
And MyViewModel as the ViewModel
public class MyViewModel
{
public MyViewModel()
{
Model = new MyModel1();
}
public MyModel1 Model { get; set; }
}
And the code behind does nothing but instantiate the ViewModel.
public partial class MainWindow : Window
{
public MainWindow()
{
ViewModel = new MyViewModel();
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
All three are very simple classes. The fun comes in the Xaml which is
<Window x:Class="StackOverflow._20893945.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:this="clr-namespace:StackOverflow._20893945"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="MyModel1Template1" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 1"></TextBlock>
<ComboBox ItemsSource="{Binding Path=Values}" SelectedItem="{Binding Path=Field1}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MyModel1Template2" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 2"></TextBlock>
<TextBox Text="{Binding Path=Field2}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="2">
<ComboBox x:Name="TypeSelector">
<system:String>Template 1</system:String>
<system:String>Template 2</system:String>
</ComboBox>
</StackPanel>
<ContentControl Content="{Binding Path=Model}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TypeSelector, Path=SelectedItem}" Value="Template 2">
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template2}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template1}" />
</Style>
</ContentControl.Style>
</ContentControl>
</DockPanel>
</Window>
The notable points of the view are
The DataContext is initialised on the Window element, allowing for auto-complete on our binding expressions
The definition of 2 template to display 2 different views of the data.
The ComboBox is populated with a list of strings and has a default selection of the first element.
The ContentControl has its content bound to the Model exposed via the ViewModel
The default DataTemplate is the first template with a ComboBox.
The Trigger in the ContentControl's style will change the ContentTemplate if the SelectedItem of the ComboBox is changed to 'Template 2'
Implied facts are
If the SelectedItem changes back to 'Template 1', the style will revert the the ContentTemplate back to the default, ie MyModel1Template1
If there were a need for 3 separate displays, create another DataTemplate, add a string to the ComboBox and add another DataTrigger.
NOTE: This is the complete source to my example. Create a new C#/WPF project with the same classes and past the code in. It should work.
I hope this helps.

Single Left Mouse Click on TreeView Node to open a document on a different User Control

I have Tree View whose information are filled with a structure of a document.
Every single article is represented by a single TreeView Node.
The goal is to raise the click event, pass the key that identifies that precise part of the document and render the information
I have 3 problems:
1) How can I pass the information to a different User Control
2) The Double click event works (just tried with a simple textbox) but not the single left click... :(
3) How can I open the precise part of the document I select on the treeview and repeat the operation. So e.g.: I click on the article number 3, I want the document of article 3 rendered, I click on article 5 etc. etc.
Code below:
<UserControl x:Class="UserControls.DocumentViewLaw"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="900"
xmlns:controls="clr-namespace:Client.UserControls">
<Grid x:Name="grdTop">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="220"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Grid.Column="0" Grid.ColumnSpan="2">
<TreeView x:Name="treeViewStructure" HorizontalAlignment="Left" Width="200" >
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<Border x:Name="bdrTreeViewItem" HorizontalAlignment="Right" BorderThickness="2" Margin="0.5" Padding="1">
<TreeViewItem Header="{Binding Text}" x:Name="treeViewItem" HorizontalAlignment="Left" HorizontalContentAlignment="Left">
</TreeViewItem>
</Border>
<HierarchicalDataTemplate.Resources>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="MouseDoubleClick" Handler="OnTreeNodeMouseClick" />
</Style>
<Style TargetType="{x:Type Border}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="LightBlue" />
<Setter Property="BorderThickness" Value="3" />
<Setter Property="CornerRadius" Value="9" />
</Trigger>
</Style.Triggers>
</Style>
</HierarchicalDataTemplate.Resources>
<HierarchicalDataTemplate.Triggers>
<Trigger SourceName="treeViewItem" Property="IsMouseOver" Value="True">
<Setter TargetName="bdrTreeViewItem" Property="Background" Value="LightGray" />
<Setter TargetName="treeViewItem" Property="Foreground" Value="Red" />
</Trigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</StackPanel>
<StackPanel Grid.Column="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<controls:TabDocumentViewLawControl x:Name="topTabLaw" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Grid.Row="0" />
</StackPanel>
</Grid>
</UserControl>
CodeBehind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace Client.UserControls
{
public partial class DocumentViewLaw : UserControl
{
public DocumentViewLaw()
{
InitializeComponent();
}
public void SetTreeViewNodeStructure(IList<TreeViewNode> nodes)
{
//this method is recalled in MainWindow.cs where I pass the object returned by
// WCF and attached to the TreeView
this.treeViewStructure.ItemsSource = nodes;
}
public void OnTreeNodeMouseClick(object sender, RoutedEventArgs e)
{
}
}
}
Second User Control where to visualize the document:
<UserControl x:Class="Client.UserControls.TabDocumentViewLawControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:editor="clr-namespace:RichEditor;assembly=RichEditor"
mc:Ignorable="d"
d:DesignHeight="500" d:DesignWidth="500"
xmlns:vm="clr-namespace:Domain.Model.Document;assembly=Domain">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</UserControl.Resources>
<ScrollViewer Grid.Row="5" Grid.Column="1" MaxHeight="250">
<StackPanel>
<FlowDocumentReader x:Name="articoloDocumentLaw" Grid.Row="1" Document="{Binding Path=FlowDocumentArticle}"
Visibility="{Binding Path=HasArticoloVisible, Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel>
</ScrollViewer>
</UserControl>
The object that I pass to the UserControl to visualize the document and his structure in
"DocumentViewLaw" User Control is the single result of a result list
In MainWindow component I associate the data and correspondant context.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
this.login.btnLogin.Click += btnLogin_Click;
this.tabMainControl.resultListControl.RowSelected += resultListControl_RowSelected;
}
void resultListControl_RowSelected(object sender, EventArgs e)
{
AutonomySearchResult selectedDocument = (AutonomySearchResult)this.tabMainControl.resultListControl.grdResult.SelectedItem;
this.tabMainControl.topTabControl.SelectedItem = this.tabMainControl.tabResultList;
Services.ServicesClient client = new Services.ServicesClient();
var document = client.GetDocument(selectedDocument.DocKey, true);
this.tabMainControl.topTabControl.SelectedItem = this.tabMainControl.tabDocumentView;
this.tabMainControl.tabDocumentView.DataContext = document;
TreeViewFactory treeFactory = new TreeViewFactory();
var documentStructure= treeFactory.GetStructure(document.DocumentKey, document.XmlStructure, true);
this.tabMainControl.documentViewLaw.SetTreeViewNodeStructure(documentStructure);
}
public virtual void onResultClick(object sender, RoutedEventArgs e)
{
}
}
Factory of TreeView:
public class TreeViewFactory
{
public IList GetStructure(DocumentKey docKey, string structure, bool loadAllParents)
{
//business logic with LINQ2XML
}
public class TreeViewNode
{
public TreeViewNode() { }
public DocumentKey DocKey { get; set; }
public string Text { get; set; }
public IList<TreeViewNode> Children { get; set; }
}
Thank u very much in advance :)
1) How can I pass the information to a different User Control?
I assume that the articles data in the TreeView.ItemSource and the data in the second UserControl are bound from properties in a view model or class that is set as the DataContext of your Window or UserControl. From your view model, you can either bind to the SelectedItem property of the TreeView, or monitor the INotifyPropertyChanged interface to see when the property that is bound to the TreeView.ItemsSource is changed (by the user). At this point, you can load the required data and update whichever property is data bound to the second UserControl.
2) The Double click event works (just tried with a simple textbox) but not the single left click... :(
If you data bind as suggested above, then you will not need a Click handler, as you can find out when the user selects another node directly in the view model.
3) How can I open the precise part of the document I select on the treeview and repeat the operation. So e.g.: I click on the article number 3, I want the document of article 3 rendered, I click on article 5 etc. etc.
I don't really understand this part of your question. You should be able to access all of the properties of the selected item in the TreeView when binding to the view model.

How to access a named element in a control that inherits from a templated control

Hello this is similar to How to access a named element of a derived user control in silverlight? with the difference is inheriting from a templated control, not a user control.
I have a templated control called MyBaseControl
Xaml:-
<Style TargetType="Problemo:MyBaseControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Problemo:MyBaseControl">
<Grid x:Name="LayoutRoot" Background="White">
<Border Name="HeaderControl" Background="Red" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Code:-
public class MyBaseControl : Control
{
public UIElement Header { get; set; }
public MyBaseControl()
{
DefaultStyleKey = typeof(MyBaseControl);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var headerControl = GetTemplateChild("HeaderControl") as Border;
if (headerControl != null)
headerControl.Child = Header;
}
}
I have another control called myControl which inherits from MyBaseControl Control
Xaml:-
<me:MyBaseControl x:Class="Problemo.MyControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
xmlns:me="clr-namespace:Problemo"
d:DesignHeight="300" d:DesignWidth="400">
<me:MyBaseControl.Header>
<TextBlock Name="xxx" />
</me:MyBaseControl.Header>
</me:MyBaseControl>
Code:-
public partial class MyControl : MyBaseControl
{
public string Text { get; set; }
public MyControl(string text)
{
InitializeComponent();
Text = text;
Loaded += MyControl_Loaded;
}
void MyControl_Loaded(object sender, RoutedEventArgs e)
{
base.ApplyTemplate();
xxx.Text = Text;
}
}
The issue is xxx is null. How do I access the xxx control in the code behind ?
When you access the HeaderControl, that is being pulled from the ControlTemplate. The elements in the ControlTemplate are created and added as visual descendants of the control. Then the OnApplyTemplate method is called and you can access them via their name.
In the second case, you are specifically assigning a single element to the Header property. There is no way to get a "named" element in this case, as the header is being explicitly set.
You could cast the Header property directly, if you know that it's going to be a TextBlock, like so:
TextBlock tb = this.Header as TextBlock;
if (tb != null)
tb.Text = Text;
Otherwise, you could bind the TextBlock to your Text property in your XAML, like so:
<me:MyBaseControl.Header>
<TextBlock Name="xxx" Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type me:MyControl}}, Path=Text}" />
</me:MyBaseControl.Header>
The latter method of binding is the better way to go, since you are not tied to a given control (i.e. TextBlock).

Resources