In my MVVM app I have a treeview representing records in a database. My views and viewmodels are linked in a resource dictionary like this
<DataTemplate DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
I want to display a preview of the view when a user hovers over an icon using the tooltip. My HierarchicalDataTemplate in the treeview is this
<HierarchicalDataTemplate DataType="{x:Type vm:TrialSiteViewModel}"
ItemsSource="{Binding Path=Children}">
...
<Button Style="{StaticResource previewButtonStyle}">
<Button.ToolTip>
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>
</ToolTip>
</Button.ToolTip>
</Button>
</StackPanel>
</HierarchicalDataTemplate>
This correctly picks up the TrialSiteViewModel that is the DataContext for the Treeviewitem.
ObjectPreview uses a viewbox and contentcontrol to display the view of the record
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}">
</ContentControl>
</Viewbox>
and the code behind contains the dependency property
public partial class ObjectPreview : UserControl
{
public ObjectPreview()
{
InitializeComponent();
}
public static readonly DependencyProperty _previewObjectProperty =
DependencyProperty.Register("PreviewObject", typeof(TreeViewItemViewModel), typeof(ObjectPreview));
public TreeViewItemViewModel PreviewObject
{
get { return (TreeViewItemViewModel)GetValue(_previewObjectProperty); }
set { SetValue(_previewObjectProperty, value); }
}
}
The problem I'm having is that the Template used to display the object is the same as that used in the treeview. This simply shows an icon and an object summary (ie. Primary Key and one or two key fields) rather than the entire template as defined in the view TrialSiteView. If I amend the code to use a button Command on the TrialSiteViewModel and inject it into ObjectPreview I can set the contentcontrol in the code behind and the TrialSiteView is used.
I'm guessing that somehow the Template is inferred from the TreeViewItem. Can anyone tell me how I can ensure the tooltip uses the TrialSiteView?
UPDATE
Ok, so I've fixed this but had to resort to code behind and removed the usercontrol and put the view directly in the tooltip. The key bit is getting the datatemplate from the resources. I'd tried to do this previously by assigning a key to the datatemplate, but either my code was flawed or it did not work. Anyhow, this works but is not the preferred Xaml solution.
private void PreviewObject_MouseEnter(object sender, MouseEventArgs e)
{
Image image = (Image)sender;
var key = new System.Windows.DataTemplateKey(image.DataContext.GetType());
var datatemplate = (DataTemplate)this.FindResource(key);
ToolTip tooltip = new ToolTip();
tooltip.Style = VisualUtils.GetResource<Style>("ControlTemplates.xaml", "toolTipWithContentStyle");
tooltip.MaxWidth = 460;
ContentControl contentcontrol = new ContentControl();
contentcontrol.ContentTemplate = datatemplate;
contentcontrol.Content = image.DataContext as TreeViewItemViewModel;
Viewbox viewbox = new Viewbox();
viewbox.Stretch = Stretch.Uniform;
viewbox.Child = contentcontrol;
tooltip.Content = viewbox;
image.ToolTip = tooltip;
}
What you need to do is to specify explicitly what data template to use. In order to do that just add a template property along with the PreviewObject property in the preview control:
public static readonly DependencyProperty _previewObjectTemplateProperty =
DependencyProperty.Register("PreviewObjectTemplate", typeof(DataTemplate), typeof(ObjectPreview));
public DataTemplate PreviewObjectTemplate
{
get { return (DataTemplate)GetValue(_previewObjectTemplateProperty); }
set { SetValue(_previewObjectTemplateProperty, value); }
}
Then, in the ObjectPreview.xaml add the ContentTemplate property that is bound to the PreviewObjectTemplate property:
<Viewbox Grid.Row="1" Name="treeviewViewBox"
Stretch="Uniform"
IsEnabled="False">
<ContentControl Name="treeViewItemViewModel"
Content="{Binding PreviewObject}"
ContentTemplate="{Binding PreviewObjectTemplate}" >
</ContentControl>
</Viewbox>
And finally, give a key to your data template and specify a reference to it explicitly when you declare ObjectPreview:
<DataTemplate x:Key="FullViewTemplate" DataType="{x:Type vm:TrialSiteViewModel}">
<vw:TrialSiteView />
</DataTemplate>
...
<ToolTip Style="{x:Null}">
<ToolTip.ContentTemplate>
<DataTemplate>
<localtools:ObjectPreview
PreviewObject="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, Path=DataContext}"
PreviewObjectTemplate="{StaticResource FullViewTemplate}"
/>
</DataTemplate>
</ToolTip.ContentTemplate>
Related
I'm fairly new to XAML but enjoying learning it. The thing I'm really struggling with is binding a property to an element in a DataTemplate.
I have created a simple WPF example to, (hopefully,) explain my problem.
I this example I am trying to bind the Visibility property of a CheckBox in a DataTemplate to a Property in my viewmodel. (Using this scenario purely for learning/demo.)
I have a simple DataModel named Item, but is of little relevance in this example.
class Item : INotifyPropertyChanged
{
// Fields...
private bool _IsRequired;
private string _ItemName;
And a fairly simple View Model named ItemViewModel.
class ItemViewModel : INotifyPropertyChanged
{
private ObservableCollection<Item> _Items;
private bool _IsCheckBoxChecked;
private bool _IsCheckBoxVisible;
public ObservableCollection<Item> Items
{
get { return _Items; }
set { _Items = value; }
}
public bool IsCheckBoxChecked
{
get { return _IsCheckBoxChecked; }
set
{
if (_IsCheckBoxChecked == value)
return;
_IsCheckBoxChecked = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxChecked"));
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
}
}
}
public bool IsCheckBoxVisible
{
get { return !_IsCheckBoxChecked; }
set
{
if (_IsCheckBoxVisible == value)
return;
_IsCheckBoxVisible = value;
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs("IsCheckBoxVisible"));
}
(Constructors and INotifyPropertyChanged implementation omitted for brevity.)
Controls laid out in MainPage.xaml as follows.
<Window.Resources>
<local:VisibilityConverter x:Key="VisibilityConverter"/>
</Window.Resources>
<Window.DataContext>
<local:ItemViewModel/>
</Window.DataContext>
<Grid>
<StackPanel>
<CheckBox x:Name="checkBox" Content="Hide CheckBoxes" FontSize="14" IsChecked="{Binding IsCheckBoxChecked, Mode=TwoWay}" />
<ListView ItemsSource="{Binding Items}" HorizontalContentAlignment="Stretch" >
<ListView.ItemTemplate >
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding ItemName}"/>
<CheckBox Grid.Column="1" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" >
<CheckBox.DataContext>
<local:ItemViewModel/>
</CheckBox.DataContext>
</CheckBox>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackPanel Orientation="Horizontal" Margin="4,4,0,0">
<TextBlock Text="IsCheckBoxVisible:"/>
<TextBlock Text="{Binding IsCheckBoxVisible}" Margin="4,0,0,0" FontWeight="Bold" />
</StackPanel >
<Button Content="Button" Visibility="{Binding IsCheckBoxVisible, Converter={StaticResource VisibilityConverter}}" Margin="4,4,4,4"/>
</StackPanel>
</Grid>
The 'Hide CheckBoxes' checkbox is bound to IsCheckBoxChecked and is used to update IsCheckBoxVisible. I've also added a couple of extra controls below the DataTemplate to prove, (to myself,) the everything works.)
I have also implemented Jeff Wilcox's value converter. (Thank you.) http://www.jeff.wilcox.name/2008/07/visibility-type-converter/
When I run the app, checking and unchecking the 'Hide Checkbox', controls outside the DataTemplate function as expected but, alas, the Checkbox inside the data template remains unchanged.
I have had success with:
IsVisible="{Binding IsChecked, Converter={StaticResource VisibilityConverter}, ElementName=checkBox}"
But I'm not just trying mimic another control but make decisions based on a value.
I would REALLY appreciate any help or advice you can offer.
Thank you.
When you are in a DataTemplate, your DataContext is the data templated object, in this case an Item. Thus, the DataContext of the CheckBox in the DataTemplate is an Item, not your ItemViewModel. You can see this by your <TextBlock Text="{Binding ItemName}"/>, which binds to a property on the Item class. The Binding to IsCheckBoxVisible is trying to find a property called IsCheckBoxVisible on Item.
There are a couple of ways around this, but by far the easiest is to do this:
On your Window (in the xaml), give it and x:Name. Eg:
<Window [...blah blah...]
x:Name="MyWindow">
Change your binding to look like this:
<CheckBox Grid.Column="1"
Visibility="{Binding DataContext.IsCheckBoxVisible, ElementName=MyWindow, Converter={StaticResource VisibilityConverter}}">
We're using the Window as the source for the Binding, then looking at its DataContext property (which should be your ItemViewModel, and then pulling off the IsCheckBoxVisible property.
Another option, if you want something fancier, is to use a proxy object to reference your DataContext. See this article on DataContextProxy.
I have created a template for multiple listboxitems and some textblocks inside. In the settings the user can change the background of the app to black or white (and then the textblock foreground color should change the opposite accordingly). How can I bind the textblocks text to one property (of the itemlist (observablecollection)) and the foreground to another property (with a converter for the color) which is not in the same datacontext (but in the settings-datacontext)?
What I am trying to do:
<DataTemplate x:Key="ArticleItemTemplateClassic">
<Grid>
<!-- ... --->
<TextBlock Text="{Binding Description}"
Foreground="{Binding SettingsFile.BlackBackgroundEnabled,
Converter={StaticResource InverseBackgroundColorConverter}}"/>
<!-- The Context of the Foreground (SettingsFile.BlackBackgroundEnabled) -->
<!-- should be not the same as where I bind Description -->
</StackPanel>
<!-- ... --->
</Grid>
</DataTemplate>
Thank you!
To do this, you will need to specify the Source of the Binding for the Foreground property. This can be done in many ways, but one example is to expose your Settings class as a resource.
For example:
<Grid x:Name="LayoutRoot">
<Grid.Resources>
<!-- If you want to use SettingsFile as a static, you might want to expose an accessor/wrapper class for it here instead. -->
<settings:SettingsFile x:Name="SettingsFileResource" />
</Grid.Resources>
<ListBox ItemsSource="{Binding MyItems}">
<ListBox.ItemTemplate>
<DataTemplate x:Key="ArticleItemTemplateClassic">
<Grid>
<!-- ... -->
<TextBlock Text="{Binding Description}"
<!-- Now change your Binding Path to the target property, and set the source to the resource defined above. -->
Foreground="{Binding BlackBackgroundEnabled, Source={StaticResource SettingsFileResource}, Converter={StaticResource InverseBackgroundColorConverter}}"/>
<StackPanel />
<!-- ... -->
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Alternativelty, it might be cleaner to use an AttachedProperty for this instead. EG:
public static bool GetBlackBackgroundEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(BlackBackgroundEnabledProperty);
}
public static void SetBlackBackgroundEnabled(DependencyObject obj, bool value)
{
obj.SetValue(BlackBackgroundEnabledProperty, value);
}
// Using a DependencyProperty as the backing store for BlackBackgroundEnabled. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BlackBackgroundEnabledProperty =
DependencyProperty.RegisterAttached("BlackBackgroundEnabled", typeof(bool), typeof(Control), new PropertyMetadata(false, (s, e) =>
{
Control target = s as Control;
SolidColorBrush brush = new SolidColorBrush();
// Logic to determine the color goes here
if (GetBlackBackgroundEnabled(target))
{
brush.Color = something;
}
else
{
brush.Color = somethingElse;
}
target.Foreground = brush;
}));
Then you would use it like this:
<TextBlock settings:SettingsFile.BlackBackgroundEnabled="True" />
You can explicitly specify a different DataContext for each item, if you are forced to do so. Although I am not sure why would you have two properties that align with the appearance of the same DataTemplate to be located in different containers.
I have a strange styling problem that I cannot figure out.
What I want to achieve is that HyperlinkButtons in datagrid cells are styled with an underline when the mouse hovers the link.
I have one column where I declare the HyperlinkButton element in XAML like this:
<HyperlinkButton Style="{StaticResource HyperlinkButtonStyle}" DataContext="{Binding}" FontSize="11" Content="{Binding DguNr}" Click="DgunrHyperlinkButtonClick" />
This works fine - the link is styled as I want.
In another column, I need to display n number of HyperlinkButtons based on some information in the bound element. Hence I have created a usercontrol that will render 0..n Hyperlinkbuttons. The control is declared in XAML like this:
<sdk:DataGridTemplateColumn IsReadOnly="True" CanUserSort="True">
<sdk:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<bc:BoreholePlantGridColumn Plants="{Binding Plants, Mode=OneWay}" />
</DataTemplate>
</sdk:DataGridTemplateColumn.CellTemplate>
</sdk:DataGridTemplateColumn>
The code behind for the control looks like this:
public partial class BoreholePlantGridColumn : UserControl, INotifyPropertyChanged
{
public BoreholePlantGridColumn()
{
InitializeComponent();
Loaded += new RoutedEventHandler(BoreholePlantGridColumn_Loaded);
}
void BoreholePlantGridColumn_Loaded(object sender, RoutedEventArgs e)
{
var borehole = (SelectableBoring)this.DataContext;
foreach(var p in borehole.Plants)
{
// <HyperlinkButton HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" DataContext="{Binding}" Foreground="Black" FontSize="11" Content="{Binding DguNr}" Click="DgunrHyperlinkButtonClick" />
var button = new HyperlinkButton();
button.Content = p.PlantId;
button.Style = (Style)App.Current.Resources["HyperlinkButtonStyle"];
button.VerticalContentAlignment = VerticalAlignment.Center;
var url = String.Format(Common.Constants.Url.GeusPlantLinkTemplate, p.PlantId);
button.NavigateUri = new Uri(url, UriKind.RelativeOrAbsolute);
button.TargetName = "_blank";
LayoutRoot.Children.Add(button);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public static readonly DependencyProperty PlantsProperty = DependencyProperty.Register("Value", typeof(Anlaeg), typeof(BoreholePlantGridColumn), new PropertyMetadata(new PropertyChangedCallback(ValueChanged)));
public IList<Anlaeg> Plants
{
get { return (IList<Anlaeg>)this.GetValue(PlantsProperty); }
set { this.SetValue(PlantsProperty, value); }
}
private static void ValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var myUC = (BoreholePlantGridColumn)obj;
var newValue = (IList<Anlaeg>)e.NewValue;
}
}
This works almost as expected; the linkbuttons(s) are rendered with the correct color - but there is not displayed an underline text when the mouse hovers the link.
I do not understand why the underline is displayed in the hyperlinkbutton that is declared directly in XAML but not in the hyperlink that is rendered in code-behind. Can someone please help me on this?
I have used the hyperlinkbutton styling from this thread:1
I just use the Following Code
<ListBox Grid.Column="1" ItemsSource="{Binding EncyclopediaList}" HorizontalContentAlignment ="Stretch" Margin="5,0" >
<ListBox.ItemTemplate>
<DataTemplate>
<HyperlinkButton Content="{Binding Name}" Foreground="Black" Command="{Binding ViewArticlePageCommand, Source={StaticResource EncyclopediaViewModel}}" CommandParameter="{Binding ServerEncyclopediaID}" FontFamily="Arial" FontSize="18" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
No need to Style the HyperlinkButton its Works perfectly in My Case Add the Underline and Make it bold when Mouse move over it.
In the current project we work on, we have a main window with several views (each with its own viewmodel) that are presented as items in a tab control. E.g: One tab item is an editor, and contains the editor view as follows:
<TabItem Header="Test Editor">
<TestEditor:TestEditorView DataContext="{Binding TestEditorViewModel}"/>
</TabItem>
Another one shows results:
<TabItem Header="Results Viewer">
<ResultViewer:ResultViewer x:Name="resultViewer1" DataContext="{Binding Path=ResultViewModel}" />
</TabItem>
etc.
I'd like to have the TabItems bound to something in the main window's viewmodel, but I can't figure out how to bind the view's name to any property without breaking the MVVM pattern. I'd like to have something like:
<TabControl.ContentTemplate>
<DataTemplate>
<TestEditor:TestEditorView DataContext ="{Binding TabDataContext}"/>
</DataTemplate>
</TabControl.ContentTemplate>
only with some binding instead of having to know at design time what type will be used as content.
Any ideas?
Usually I have the TabControl's Tabs stored in the ViewModel, along with the SelectedIndex, then I use DataTemplates to determine which View to display
View:
<Window>
<Window.Resources>
<DataTemplate DataType="{x:Type ResultViewModel}">
<ResultViewer:ResultViewer />
</DataTemplate>
<DataTemplate DataType="{x:Type EditorViewModel}">
<TestEditor:TestEditorView />
</DataTemplate>
</Window.Resources>
<TabControl ItemsSource="{Binding TabCollection}"
SelectedIndex="{Binding SelectedTabIndex}" />
</Window>
ViewModel:
public class MyViewModel : ViewModelBase
{
publicMyViewModel()
{
TabCollection.Add(new ResultsViewModel());
TabCollection.Add(new EditorViewModel());
SelectedTabIndex = 0;
}
private ObservableCollection<ViewModelBase> _tabCollection
= new ObservableCollection<ViewModelBase>();
public ObservableCollection<ViewModelBase> TabCollection
{
get { return _tabCollection };
}
private int _selectedTabIndex;
public int SelectedTabIndex
{
get { return _selectedTabIndex; }
set
{
if (value != _selectedTabIndex)
{
_selectedTabIndex = value;
RaisePropertyChanged("SelectedTabIndex");
}
}
}
}
I'm using the datagrid from wpf 4.0. This has a TemplateColumn containing a checkbox. The IsChecked property of the checkbox is set via binding.
The problem is that even if I specify the binding mode explicitly to be TwoWay, it works only in one direction.
I have to mention that the same code works perfectly in .net 3.5 with the datagrid from the wpf toolkit.
Please take a look at the .xaml and .cs contents.
Thanks in advance,
Roland
<Window.Resources>
<DataTemplate
x:Key="IsSelectedColumnTemplate">
<CheckBox
IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"
/>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid
x:Name="dataGrid"
AutoGenerateColumns="false"
CanUserAddRows="False"
CanUserDeleteRows="False"
HeadersVisibility="Column"
ItemsSource="{Binding}"
>
<DataGrid.Columns>
<DataGridTemplateColumn
Header="Preselected"
x:Name="myIsSelectedColumn"
CellTemplate="{StaticResource IsSelectedColumnTemplate}"
CanUserSort="True"
SortMemberPath="Orientation"
Width="Auto"
/>
</DataGrid.Columns>
</DataGrid>
</Grid>
and the related .cs content:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<DataObject> DataSource = new ObservableCollection<DataObject>();
DataSource.Add(new DataObject());
DataSource.Add(new DataObject());
dataGrid.ItemsSource = DataSource;
}
}
public class DataObject : DependencyObject
{
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSelected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(DataObject), new UIPropertyMetadata(false, OnIsSelectedChanged));
private static void OnIsSelectedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
// this part is not reached
}
}
You set UpdateSourceTrigger=PropertyChanged in your Checkbox IsChecked binding in the datatemplate:
<CheckBox
IsChecked="{Binding Path=IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
/>
Here's the deal, the way the data grid works, is that it creates a data view and displays it instead of the original data, therefore when you simply bind a property in the CellTemplate it doesn't get propagated from the view to the data.
What you need to do is use the CellEditingTemplate so that the data grid knows when you're editing, and can propagate it to the data when done (or it can undo it if you cancel).
Here's the modified XAML for you:
<Window.Resources>
<DataTemplate x:Key="IsSelectedColumnTemplate">
<TextBlock Text="{Binding IsSelected}"/>
</DataTemplate>
<DataTemplate x:Key="IsSelectedColumnTemplateEditing">
<CheckBox IsChecked="{Binding Path=IsSelected, Mode=TwoWay}"/>
</DataTemplate>
</Window.Resources>
...
<DataGridTemplateColumn
Header="Preselected"
x:Name="myIsSelectedColumn"
CellTemplate="{StaticResource IsSelectedColumnTemplate}"
CellEditingTemplate="{StaticResource IsSelectedColumnTemplateEditing}"
CanUserSort="True"
Width="Auto"
/>
...