I have some nested controls on a wpf TabControl.
I simply need to find the specific TabItem which is the parent of the control. Preferably using LINQ. Any ideas.
A non-selected TabItem don't exist in the Visual tree, but in the Logical tree.
I've made a small example on this:
<TabControl x:Name="TabControl">
<TabItem Header="Test 1">
<Button Content="Click to get TabItem Name" Margin="100" Click="btnGetParent_Click"/>
</TabItem>
<TabItem Header="Test 2"/>
</TabControl>
Clicking on the Button inside the TabItem should display the TabItem's header meaning you have access to it and you would be able to do any work you need.
I've kept the code pretty simple here too:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnGetParent_Click(object sender, RoutedEventArgs e)
{
TabItem ti = TryFindParent<TabItem>(sender as Button);
MessageBox.Show(ti.Header.ToString());
}
}
And this is the helper method:
public T TryFindParent<T>(DependencyObject child)
where T : DependencyObject
{
DependencyObject parentObject = LogicalTreeHelper.GetParent(child);
if (parentObject == null) return null;
T parent = parentObject as T;
if (parent != null)
return parent;
else
return TryFindParent<T>(parentObject);
}
The call to LogicalTreeHelper instead of the usual VisualTreeHelper does the job here.
Give it a try, i hope this helps.
Related
Switching for the first time ever from WPF to UWP I found out that there are great controls like SplitView and NavgiationView do exist in the world of UWP.
For renewing my current home-project I have choosen the NavigationView control as my main UI control for offering the various pieces of information.
Using the code-behind the page navigation (as shown here) is very easy, but for my use-case I want to work with MVVM (as a learning procedure without using FWs like MVVMLIght or similar).
Currently, my NavigationView looks like this; but I have not a proper idea how to change within the frame through pages (from my understanding I have to use the NavigationService but haven't found a easy to understand example of this):
<NavigationView x:Name="nvTopLevelNav" Grid.Column="0" Grid.Row="1" Grid.RowSpan="3" IsPaneOpen="False" IsPaneToggleButtonVisible="False" CompactModeThresholdWidth="0" IsBackButtonVisible="Collapsed" Background="Black" Foreground="Black"
Loaded="nvTopLevelNav_Loaded"
Margin="0,12,0,0"
SelectionChanged="nvTopLevelNav_SelectionChanged"
ItemInvoked="nvTopLevelNav_ItemInvoked"
IsTabStop="False"
IsSettingsVisible="False"
AlwaysShowHeader="False"
Header="asdasdaasdasdasd">
<NavigationView.MenuItems>
<NavigationViewItem Icon="Home" Content="Home" Tag="Home_Page" />
<NavigationViewItem Icon="Globe" Content="Weather" Tag="Weather_Page" />
<NavigationViewItem Content="My Agenda" Tag="Agenda_Page">
<!-- some custom PathIcon -->
</NavigationViewItem>
<NavigationViewItem Icon="Contact" Content="My News" Tag="News_Page" />
</NavigationView.MenuItems>
<Frame x:Name="contentFrame"></Frame>
</NavigationView>
UWP NavigationView switch to another page via MVVM
For your requirement, you could use Windows Template Studio to create UWP project that contain MVVM pattern and NavigationService.
private void OnItemInvoked(NavigationViewItemInvokedEventArgs args)
{
if (args.IsSettingsInvoked)
{
NavigationService.Navigate(typeof(SettingsViewModel).FullName);
return;
}
var item = _navigationView.MenuItems
.OfType<NavigationViewItem>()
.First(menuItem => (string)menuItem.Content == (string)args.InvokedItem);
var pageKey = item.GetValue(NavHelper.NavigateToProperty) as string;
NavigationService.Navigate(pageKey);
}
Have a read of my blog about solution i came up with without any 3rd party api, there is also github repot with the solution.
https://kwodarczyk.github.io/UwpWinUI3PageMVVMNavigation/
All you need it attached property that allows to pass view model type to your datatempalte
public class ViewModelContext : DependencyObject
{
public static readonly DependencyProperty TypeProperty =
DependencyProperty.RegisterAttached(
"ViewModelContext",
typeof(Type),
typeof(ViewModelContext),
new PropertyMetadata(null)
);
public static void SetType(DependencyObject element, Type value)
{
element.SetValue(TypeProperty, value);
}
public static Type GetType(DependencyObject element)
{
return (Type)element.GetValue(TypeProperty);
}
}
example datatempalte:
<DataTemplate x:Key="PageOne" viewModels:ViewModelContext.Type="viewModels:PageOneViewModel">
<views:PageOne/>
</DataTemplate>
and custom template selector where you can match the template agatis the view model
public class CustomTemplateSelector : DataTemplateSelector
{
protected override DataTemplate SelectTemplateCore(object item, DependencyObject container)
{
if (item != null)
{
var viewModeltype = item.GetType();
foreach (var rd in Application.Current.Resources.MergedDictionaries)
{
foreach (var value in rd.Values)
{
if (value is DataTemplate dataTempate)
{
Type contexType = dataTempate.GetValue(ViewModelContext.TypeProperty) as Type;
if (contexType != null && contexType == viewModeltype)
{
return dataTempate;
}
}
}
}
}
return null;
}
}
I have been trying to implement this for a while and haven't been able to do it so far, despite having the feeling that this should be something easy.
The difficulty comes from the fact that I have implemented a WPF application using the MVVM pattern. Now, this is my first attempt at both the pattern and the framework, so it is almost guaranteed that I have made mistakes while trying to follow the MVVM guidelines.
My implementation
I have three Views with their respective ViewModels (wired using Prism's AutoWireViewModel method). The MainView has a TabControl with two TabItems, each of witch contains a Frame container with the Source set to one of the other two Views. The following code is an excerpt of the MainView:
<TabControl Grid.Row="1" Grid.Column="1">
<TabItem Header="Test">
<!--TestView-->
<Frame Source="View1.xaml"/>
</TabItem>
<TabItem Header="Results">
<!--ResultsView-->
<Frame Source="View2.xaml"/>
</TabItem>
</TabControl>
My problem
Every time that someone changes to a specific TabItem, I would like to run a method that updates one of the WPF controls included in that View. The method is already implemented and bound to a Button, but ideally, no button should be necessary, I would like to have some kind of Event to make this happen.
I appreciate all the help in advance.
You could for example handle the Loaded event of the Page to either call a method or invoke a command of the view model once the view has been loaded initially:
public partial class View2 : Page
{
public View2()
{
InitializeComponent();
Loaded += View2_Loaded;
}
private void View2_Loaded(object sender, RoutedEventArgs e)
{
var viewModel = DataContext as ViewModel2;
if (viewModel != null)
viewModel.YourCommand.Execute(null);
Loaded -= View2_Loaded;
}
}
The other option would be handle this in the MainViewModel. You bind the SelectedItem property of the TabControl to a property of the MainViewModel and set this property to an instance of either ViewModel2 or ViewModel2, depending on what kind of view you want to display.
You could then call any method or invoked any command you want on these. But this is another story and then you shouldn't hardcode the TabItems in the view and use Frame elements to display Pages. Please take a look here for an example:
Selecting TabItem in TabControl from ViewModel
Okay, so What I have done is Create a Custom Tab Control. I will write out step by step instructions for this, and then you can add edit to it.
Right click on your solution select add new project
Search For Custom Control Library
High Light the name of the class that comes up, and right click rename it to what ever you want I named it MyTabControl.
Add Prism.Wpf to the new project
Add a reference to the new project to where ever your going to need it. I needed to add to just the main application, but if you have a separate project that only has views then you will need to add it to that too.
Inherit your Custom Control From TabControl Like:
public class MyTabControl : TabControl
You will notice that there is a Themes folder in the project you will need to open the Generic.xaml and edit it. it should look like:
TargetType="{x:Type local:MyTabControl}" BasedOn="{StaticResource {x:Type TabControl}}" for some reason this will not let me show the style tags but they will need to be in there as well
Please review this code I got this from Add A Command To Custom Control
public class MyTabControl : TabControl
{
static MyTabControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyTabControl), new FrameworkPropertyMetadata(typeof(MyTabControl)));
}
public static readonly DependencyProperty TabChangedCommandProperty = DependencyProperty.Register(
"TabChangedCommand", typeof(ICommand), typeof(MyTabControl),
new PropertyMetadata((ICommand)null,
new PropertyChangedCallback(CommandCallBack)));
private static void CommandCallBack(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var myTabControl = (MyTabControl)d;
myTabControl.HookupCommands((ICommand) e.OldValue, (ICommand) e.NewValue);
}
private void HookupCommands(ICommand oldValue, ICommand newValue)
{
if (oldValue != null)
{
RemoveCommand(oldValue, oldValue);
}
AddCommand(oldValue, oldValue);
}
private void AddCommand(ICommand oldValue, ICommand newCommand)
{
EventHandler handler = new EventHandler(CanExecuteChanged);
var canExecuteChangedHandler = handler;
if (newCommand != null)
{
newCommand.CanExecuteChanged += canExecuteChangedHandler;
}
}
private void CanExecuteChanged(object sender, EventArgs e)
{
if (this.TabChangedCommand != null)
{
if (TabChangedCommand.CanExecute(null))
{
this.IsEnabled = true;
}
else
{
this.IsEnabled = false;
}
}
}
private void RemoveCommand(ICommand oldCommand, ICommand newCommand)
{
EventHandler handler = CanExecuteChanged;
oldCommand.CanExecuteChanged -= handler;
}
public ICommand TabChangedCommand
{
get { return (ICommand) GetValue(TabChangedCommandProperty); }
set { SetValue(TabChangedCommandProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.SelectionChanged += OnSelectionChanged;
}
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (TabChangedCommand != null)
{
TabChangedCommand.Execute(null);
}
}
}
you will need to add the name space in your window or usercontrol like:
xmlns:wpfCustomControlLibrary1="clr-namespace:WpfCustomControlLibrary1;assembly=WpfCustomControlLibrary1"
and here is your control:
<wpfCustomControlLibrary1:MyTabControl TabChangedCommand="{Binding TabChangedCommand}">
<TabItem Header="View A"></TabItem>
<TabItem Header="View B"></TabItem>
</wpfCustomControlLibrary1:MyTabControl>
This is how I'd approach this sort of requirement:
View:
<Window.DataContext>
<local:MainWIndowViewModel/>
</Window.DataContext>
<Grid>
<TabControl Name="tc" ItemsSource="{Binding vms}">
<TabControl.Resources>
<DataTemplate DataType="{x:Type local:uc1vm}">
<local:UserControl1/>
</DataTemplate>
<DataTemplate DataType="{x:Type local:uc2vm}">
<local:UserControl2/>
</DataTemplate>
</TabControl.Resources>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="Header" Value="{Binding TabHeading}"/>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Grid>
</Window>
When it has a uc1vm it will be templated into usercontrol1 in the view.
I'm binding to a collection of viewmodels which all implement an interface so I know for sure I can cast to that and call a method.
Main viewmodel for window:
private IDoSomething selectedVM;
public IDoSomething SelectedVM
{
get { return selectedVM; }
set
{
selectedVM = value;
selectedVM.doit();
RaisePropertyChanged();
}
}
public ObservableCollection<IDoSomething> vms { get; set; } = new ObservableCollection<IDoSomething>
{ new uc1vm(),
new uc2vm()
};
public MainWIndowViewModel()
{
}
When a tab is selected, the setter for selected item will be passed the new value. Cast that and call the method.
My interface is very simple, since this is just illustrative:
public interface IDoSomething
{
void doit();
}
An example viewmodel, which is again just illustrative and doesn't do much:
public class uc1vm : IDoSomething
{
public string TabHeading { get; set; } = "Uc1";
public void doit()
{
// Your code goes here
}
}
I appreciate all of your input, but I found an alternative solution. Given the information given by #mm8, I took advantage of the Loaded event but in a way that does not require any code in the code behind.
My solution
In the View which I would like to give this ability to execute a method every time the user selects the TabItem that contains it, I added the following code:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding OnLoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
And then simply implemented a DelegateCommand called OnLoadedCommand in the View's respective ViewModel. Inside that command I call my desired method.
Please comment if you spot anything wrong with this approach! I chose to try this since it required the least amount of changes to my code, but I may be missing some vital information regarding problems the solution may cause.
In WPF with MVVM it's easy to fire some code when the user changes the tab.
<TabControl Margin="0 5 5 5" Background="#66F9F9F9" SelectedIndex="{Binding TabIndex}">
And then in the ViewModel:
private int _tabIndex;
public int TabIndex
{
get { return _tabIndex; }
set
{
if(_tabIndex != value)
{
_tabIndex = value;
OnPropertyChanged("TabIndex");
if(value == 1)
{
//do something
}
}
}
}
But I'm vaguely uncomfortable with this. What if another developer happens along later and adds another tab in the "1" position. If this is application-critical code (which it is), things will break spectacularly.
Danger can be minimized with unit tests, of course. But it made me wonder: is this seen as bad practice? And is there a way of doing this that allows you to refer to the Tab with a string, rather than an int? I tried noodling with binding to the SelectedValue property, but nothing seemed to happen when the tabs were changed.
You could make a behavior for TabItem, listening for changes to the IsSelected dependency property, and raises a Command when the tab is selected. This can be extended to any number of tabs, each which invokes different commands in the viewmodel. You could also supply a command parameter for any optional context:
class TabSelectedBehavior : Behavior<TabItem>
{
public static readonly DependencyProperty SelectedCommandProperty = DependencyProperty.Register("SelectedCommand", typeof(ICommand), typeof(TabSelectedBehavior));
public ICommand SelectedCommand
{
get { return (ICommand)GetValue(SelectedCommandProperty); }
set { SetValue(SelectedCommandProperty, value); }
}
private EventHandler _selectedHandler;
protected override void OnAttached()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null)
{
_selectedHandler = new EventHandler(AssociatedObject_SelectedChanged);
dpd.AddValueChanged(AssociatedObject, _selectedHandler);
}
base.OnAttached();
}
protected override void OnDetaching()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null && _selectedHandler != null)
{
dpd.RemoveValueChanged(AssociatedObject, _selectedHandler);
}
base.OnDetaching();
}
void AssociatedObject_SelectedChanged(object sender, EventArgs e)
{
if (AssociatedObject.IsSelected)
{
if (SelectedCommand != null)
{
SelectedCommand.Execute(null);
}
}
}
}
XAML
<TabControl>
<TabItem Header="TabItem1">
<i:Interaction.Behaviors>
<local:TabSelectedBehavior SelectedCommand="{Binding TabSelectedCommand}"/>
</i:Interaction.Behaviors>
</TabItem>
<TabItem Header="TabItem2">
</TabItem>
</TabControl>
In a similar fashion you could also make a behavior for the TabControl, which turns the SelectionChanged event into a command, and pass the Tag object of the selected TabItem as command parameter.
As with all collection controls, the best way to maintain the selected item is to use the SelectedItem property. If you data bind a property of the relevant data type to the TabControl.SelectedItem property, then you'll still be able to tell which tab is selected and select a different one from the view model.
The only problem with this method is that you'll also need to use the TabControl.ItemsSource property to set up the TabItems:
<TabControl ItemsSource="{Binding YourDataItems}" SelectedItem="{Binding YourItem}" />
If you want to try this, then you should know that defining the TabItems can be a little bit confusing. Please refer to the answer from the How to bind items of a TabControl to an observable collection in wpf? question for help with that.
I have a MainWindow.xaml, MainwindowViewModel.cs, HaemogramReport.xaml and HaemogramReport.xaml.cs. I have other files as well in my project, but the problem lies in the above mentioned four files.
I am posting the minimal code here so that others can catch the problem.
Now in HaemogramReport.xaml I declare some controls like Grid, TextBox, TextBlock, Rectangle, Border, ContentControl etc.
For example HaemogramReport.xaml looks like:
<Page.DataContext>
<vm:MainWindowViewModel />
</Page.DataContext>
<Grid DataContext="{Binding Source={StaticResource Settings}}" PreviewMouseDown="Object_Selection" x:Name="Root">
<Border Style="{StaticResource BorderStyle}" x:Name="HaemogramTestBorder"
Grid.Row="{Binding Default.HaemogramTestGridRow}" Grid.Column="{Binding Default.HaemogramTestGridColumn}"
Grid.RowSpan="{Binding Default.HaemogramTestGridRowSpan}" Grid.ColumnSpan="{Binding Default.HaemogramTestGridColumnSpan}">
<Grid>
<Rectangle Fill="Transparent" x:Name="HaemogramTestRectangle"/>
<TextBlock x:Name="HaemogramTestTextBlock"
Text="{Binding Default.HaemogramTestText}" Visibility="{Binding Default.HaemogramTestVisibility}"
Background="{Binding Default.HaemogramTestBackground, Converter={StaticResource colorToSolidColorBrushConverter}}"
Foreground="{Binding Default.HaemogramTestForeground, Converter={StaticResource colorToSolidColorBrushConverter}}"
FontFamily="{Binding Default.HaemogramTestFontFamily, Converter={StaticResource stringToFontFamilyConverter}}"
FontSize="{Binding Default.HaemogramTestFontSize}"
FontWeight="{Binding Default.HaemogramTestFontWeight}" FontStyle="{Binding Default.HaemogramTestFontStyle}"
HorizontalAlignment="{Binding Default.HaemogramTestHorizontalAlignment}"
VerticalAlignment="{Binding Default.HaemogramTestVerticalAlignment}"
Margin="{Binding Default.HaemogramTestMargin}" />
</Grid>
</Border>
</Grid>
When I click on any of the element in the above declared elements, the mousedown event of the grid named Root is raised.
That event handler is in HaemogramReport.xmal.cs. Here it is:
private void Object_Selection(object sender, MouseButtonEventArgs e)
{
var mouseWasDownOn = e.Source as FrameworkElement;
if (mouseWasDownOn != null)
{
foreach (Border border in FindVisualChildren<Border>(Root))
{
border.BorderBrush = Brushes.Transparent;
}
if (!(mouseWasDownOn is Border))
{
FindParent<Border>(mouseWasDownOn).BorderBrush = Brushes.Orange;
}
MainWindowViewModel mwvm = new MainWindowViewModel();
mwvm.SelectedObj = mouseWasDownOn;
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
In mouseDown handler of Grid named Root, I say mwvm.SelectedObj = mouseWasDownOn;
SelectedObj is a property of type FrameworkElement which is declared in MainwindowViewModel.cs as follows:
private FrameworkElement selectedObj;
public FrameworkElement SelectedObj
{
get
{
return selectedObj;
}
set
{
selectedObj = value;
OnPropertyChanged("SelectedObj");
}
}
Now in my MainWindow I have for example a grid and a textBox inside it. The problematic bindings are declared here. xaml looks like:
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid DataContext="{Binding SelectedObj, UpdateSourceTrigger=PropertyChanged}">
<TextBox Text="{Binding Text, UpdateSourceTrigger=PropertyChanged, TargetNullValue='null', FallbackValue='Error'}"/>
</Grid>
When using the above code, I always get the Text Error in above TextBox.
At the first chance I thought that this might be the binding error, so I changed my MainWindowViewModel.cs as follows:
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
SelectedObj = txt;
}
TextBlock txt = new TextBlock()
{
Text = "123"
};
private FrameworkElement selectedObj;
public FrameworkElement SelectedObj
{
get
{
return selectedObj;
}
set
{
selectedObj = value;
OnPropertyChanged("SelectedObj");
}
}
}
After making the above changes when I run my project I can see 123 in textbox but when I click on any element the text in the textbox does not change.
Now the question here is that if its a binding error then why in second example I get 123 in textbox while in 1st example I get Error - the fallback value.
And if it's not a binding error then what is the problem in above code?
Update
When I debug, I found that get part of SelectedObj is never called. But I don't know why?
Update -- Reed Copsey
Here is my new class:
public class DesignMethods
{
public static void FindCurrentlyClickedElement(DependencyObject Root, MouseButtonEventArgs e, MainWindowViewModel vm)
{
var mouseWasDownOn = e.OriginalSource as FrameworkElement;
if (mouseWasDownOn != null)
{
foreach (Border border in FindVisualChildren<Border>(Root))
{
border.BorderBrush = Brushes.Transparent;
}
if (!(mouseWasDownOn is Border))
{
FindParent<Border>(mouseWasDownOn).BorderBrush = Brushes.Orange;
}
vm.SelectedObj = mouseWasDownOn;
}
}
public static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
public static T FindParent<T>(DependencyObject child) where T : DependencyObject
{
//get parent item
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
//we've reached the end of the tree
if (parentObject == null) return null;
//check if the parent matches the type we're looking for
T parent = parentObject as T;
if (parent != null)
return parent;
else
return FindParent<T>(parentObject);
}
}
And I use it like:
private void Object_Selection(object sender, MouseButtonEventArgs e)
{
DesignMethods.FindCurrentlyClickedElement(Root, e, this.DataContext as MainWindowViewModel);
}
The problem is you're creating a new instance of the ViewModel, not using the existing one:
// This is not the same instance you're binding to!
// MainWindowViewModel mwvm = new MainWindowViewModel();
// Get the existing one instead
var mwvm = this.DataContext as MainWindowViewModel;
mwvm.SelectedObj = mouseWasDownOn;
Note that I would likely not use the term "ViewModel" here, though. What you are doing is very much not a typical MVVM scenario as you're tightly coupling your DataContext instance into your View, with coupling happening in both directions, which is pretty much the opposite of the normal goals of MVVM.
Edit:
You may also need to update your bindings for SelectedObj. I would recommend trying with the XAML set to:
<Grid>
<TextBox Text="{Binding SelectedObj.Text, UpdateSourceTrigger=PropertyChanged, TargetNullValue='null', FallbackValue='Error'}"/>
</Grid>
Try to use the OriginalSource instead of Source:
var mouseWasDownOn = e.OriginalSource as FrameworkElement;
because the Source property when dealing with composite controls, can be the parent that contains the OriginalSource object (in your case the grid).
I think your error might be that FrameworkElement doesn't have a Text property http://msdn.microsoft.com/en-us/library/system.windows.frameworkelement(v=vs.110).aspx
EDIT: Try updating your binding on Text to be
{Binding SelectedObj.Text}
I think your mistake might be that you are using "common" properties instead of DependencyProperties.
As you can see on Microsoft Description
"When you define your own properties and want them to support many
aspects of Windows Presentation Foundation (WPF) functionality,
including styles, data binding, inheritance, animation, and default
values, you should implement them as a dependency property."
These are the correct types of property to fully use all resources provided by WPF
Take a look at these links
http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty(v=vs.110).aspx
http://msdn.microsoft.com/en-us/library/ms750428(v=vs.110).aspx
Or simply look for Dependency Property WCF on google.
Another useful link to understand the difference between these properties is
https://stackoverflow.com/a/3674727
which I used when I had similar problems.
Hope it helps!
I'm using a RadGridView to display a bunch of items in a grid. For each item, I want to switch between two different templates based on the data being given. One is a dependency property which essentially pops a text block in, the other is another RadGridView to display a table.
When put in statically, they both work individually, but I want to dynamically select these two different templates. My selector does not get called, however, and thus no template is used.
Resources:
<Window.Resources>
<DataTemplate x:Key="theBasicView">
<controls:InfoDetailsControl InfoDetail="{Binding InfoDetails}" />
</DataTemplate>
<DataTemplate x:Key="theTableView">
<telerik:RadGridView ItemsSource="{Binding DetailsTable}" />
</DataTemplate>
<analysis:DetailsTemplateSelector
BasicView="{StaticResource theBasicView}"
TableView="{StaticResource theTableView}"
x:Key="detailsTemplateSelector"
/>
</Window.Resources>
And the template selector in question:
<telerik:RadGridView.RowDetailsTemplate>
<DataTemplate>
<ItemsControl
ItemTemplateSelector="{StaticResource detailsTemplateSelector}"
/>
</DataTemplate>
</telerik:RadGridView.RowDetailsTemplate>
If it is a BasicView, then the DetailsTable should be null. Otherwise, it should be a TableView. Here is my DetailsTemplateSelector:
public class DetailsTemplateSelector : DataTemplateSelector
{
public DataTemplate BasicView { get; set; }
public DataTemplate TableView { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null && item is ViewableRuleResult)
{
ViewableRuleResult ruleResult = item as ViewableRuleResult;
Window window = Application.Current.MainWindow;
if (ruleResult.DetailsTable == null)
{
return BasicView;
}
else
{
return TableView;
}
}
return null;
}
}
Putting a breakpoint in the SelectTemplate function never gets hit. Why is my DetailsTemplateSelector never getting called? I have a feeling that the template selector in my RowDetailsTemplate isn't right. Let me know if you need more detail or something is unclear.
Thanks!
Fixed it. Turns out RadGridView has a property RowDetailsTemplateSelector. Using the following XAML:
<telerik:RadGridView x:Name="resultsgrid"
RowDetailsTemplateSelector="{StaticResource detailsTemplateSelector}"
ItemsSource="{Binding ViewableItems}"
AutoGenerateColumns="False"
Margin="0,0,0,30"
IsReadOnly="True"
>
And completely deleting the RowDetailsTemplate previously defined, it now functions properly.