DataTemplate Binding to multiple object properties - c#

I'm using button styling, with data template, and having problem with showing text (Name and Number properties of my Table objects).
This is xaml:
<Style x:Key="btnTable" TargetType="{x:Type Button}" >
<Setter Property="Background" Value="Transparent" />
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate DataType="{x:Type my:Tables}">
<Grid>
<Image VerticalAlignment="Center" Source="/Images/Ico/table-40x40.png" />
<TextBlock Text="{Binding Path=Naziv}" />
<Ellipse Canvas.Top="30" Canvas.Left="30" Fill="#FF6A4E8C" />
<TextBlock Text="{Binding Number}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then, in .cs I create buttons like this:
Button b = new Button();
Style myStyle = (Style)Resources["btnTable"];
b.Style = myStyle;
b.DataContext = myTableItem;
If I test buttons click event, it has in DataContext property valid Table object.
I have tried replacing Binding Path width Binding Path=DataContext.Name, adding RelativeSource=Self, but nothing helps.
Additionally, I tried creating buttons using ItemsControl, but have the same issue.

b.Content = myTableItem;
It's the ContentTemplate. It's a template for the Content.

Related

Closable tab with tab item being a user control

I am working on a project in C# WPF. I have a tab container and I want to dynamically load different types of tabs into the tab container as the user requires. As an example I am doing something like the following:
tabContainer.Items.Add(new MyUserControl());
I want each tab to have a close button so the tab can be removed the container when the user no longer requires it.
I found this code project example but from what I can see you are a loading a user control which contains the xaml for the tab itself, not the tab content or am I missing something.
How can I load in my User Control into the tab container, but also have the tab closable.
Currently the tab that I am loading in uses some static binding to set the tab title using the following:
<TabControl x:Name="tabContainer" Grid.Column="2" Margin="10,45,0,0" RenderTransformOrigin="0.5,0.55" Grid.ColumnSpan="3">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
</TabControl>
My user control then has a `public string TabHeader{get;set;} which gets set in the constructor depending on what constructor of my user control is used.
You will have to define the close Button yourself. You could for example do this in the HeaderTemplate of the TabItem:
<TabControl x:Name="tabContainer">
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
<Button Content="x" Click="Button_Click_2"
Tag="{Binding DataContext, RelativeSource={RelativeSource AncestorType=TabItem}}"/>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
</TabControl>
The Tag property is bound to the UserControl in the Items collection which you can remove in the click event handler of the Button, like this:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
tabContainer.Items.Add(new MyUserControl());
}
private void Button_Click_2(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
tabContainer.Items.Remove(button.Tag);
}
}
If you want to add a close button to each tab, that would be in the TabItem style ControlTemplate. Normally you'd specify the data context (i.e. the data only that's driving the content) in Content and then specify the look in ContentTemplate. If your Content is a UserControl then you don't specify the ContentTemplate since a UserControl knows how to draw itself.
For my sins, I've added close-tab buttons to the WPF TabControl. I ended up putting the close button in the ItemTemplate. Here's a minimal version that works with the way you're populating the TabControl and the header content:
<TabControl
>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Header" Value="{Binding TabHeader}" />
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Content="{Binding}"
Grid.Column="0"
/>
<Button
VerticalAlignment="Center"
Grid.Column="1">
<Path
Data="M 0, 0 L 12, 12 M 12,0 L 0,12"
Stroke="Red"
StrokeThickness="2"
Width="12"
Height="12"
/>
</Button>
</Grid>
</DataTemplate>
</TabControl.ItemTemplate>
<local:UserControl1 TabHeader="First Item" />
<local:UserControl1 TabHeader="Second Item" />
</TabControl>

How to add a GridView from PageResource to a pivotItem dynamically in UWP?

I'm trying to create mutiple PivotItems dynamically in the c# end and then for every PivotItem I want the set the content as a GridView which has a Datatemplate where elements are bound dynamically.
XAML code:
<Page.Resources>
<GridView x:Key="pivoteItemGV"
Margin="18,10,0,0"
Background="#191919"
SelectionMode="None">
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel MaxWidth="300">
<TextBlock x:Name="ItemNameTB" Text="{Binding ItemName}"/>
<TextBlock x:Name="ItemDescriptionTB" Text="{Binding ItemDescription}"
TextWrapping="WrapWholeWords"/>
<TextBlock x:Name="ItemPriceTB" Text="{Binding ItemPrice}" />
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</Page.Resources>
And on the C# end
private void BindTheContentToPage()
{
foreach(var categoryItem in contentResourceDictionary)
{
PivotItem categoryPivotItem = new PivotItem();
categoryPivotItem.Header = categoryItem.Key;
GridView gv = this.Resources["pivoteItemGV"] as GridView;
categoryPivotItem.Content = gv;
categoryPivotItem.DataContext = categoryItem.Value;
mainContentPivot.Items.Add(categoryPivotItem);
}
}
The app crashes at categoryPivotItem.Content = gv; stating Value does not fall within the expected range.
Is what I'm doing right does? As I'm not sure whether multiple copies of Page.Resource contents, GridView in this case is duplicated.
Thanks for the help in advance.
Don't put visual elements directly into the Resources section as you did. You can put DataTemplates there.
You need to set the PivotItem.ContentTemplate property to some DataTemplate. And set the PivotItem.Content property to your data object you want to bind to.
XAML code:
<Page.Resources>
<DataTemplate x:Key="pivoteItemGVTemplate">
<GridView
ItemsSource="{Binding}"
Margin="18,10,0,0"
Background="#191919"
SelectionMode="None">
<GridView.ItemContainerStyle>
<Style TargetType="GridViewItem">
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</GridView.ItemContainerStyle>
<GridView.ItemTemplate>
<DataTemplate>
<StackPanel MaxWidth="300">
<TextBlock x:Name="ItemNameTB" Text="{Binding ItemName}"/>
<TextBlock x:Name="ItemDescriptionTB" Text="{Binding ItemDescription}"
TextWrapping="WrapWholeWords"/>
<TextBlock x:Name="ItemPriceTB" Text="{Binding ItemPrice}" />
</StackPanel>
</DataTemplate>
</GridView.ItemTemplate>
</GridView>
</DataTemplate>
</Page.Resources>
And in C# codebehind:
private void BindTheContentToPage()
{
foreach(var categoryItem in contentResourceDictionary)
{
PivotItem categoryPivotItem = new PivotItem();
categoryPivotItem.Header = categoryItem.Key;
var template = this.Resources["pivoteItemGVTemplate"] as DataTemplate;
categoryPivotItem.ContentTemplate = template;
categoryPivotItem.Content = categoryItem.Value;
mainContentPivot.Items.Add(categoryPivotItem);
}
}
If your categoryItem.Value is a List of data objects, then add an attribute to the GridView (as I did in the XAML):
ItemsSource="{Binding}"

How to change XAML elemen't template from code-behind?

I have next button's style defined in resources:
<Style x:Key="OKBtn" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Rectangle .../>
<TextBlock x:Name="Text" ..>
<Run Language="en-en" Text="OK"/>
</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I want in some specified case from code change Button's text.
I.e. change "OK" (<Run Language="en-en" Text="OK"/>) to "Accept".
How can I do that?
Is it possible to access this TextBlock "Text" and change content exactly for my one button, but not for all OK buttons?
My button:
<Button x:Name="OkButton" Style="{DynamicResource OKBtn}" />
You can borrow some props from template Template, for example Tag property. So the TextBlock text in the ControlTemplate should be like this.
<Run Language="en-en" Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Tag}"/>
And you can change the button caption by setting it's Tag property.
OkButton.Tag = "Accept";
And for not set all button texts manually you can create some ValueConverter to set TextBlock text in the ControlTemplate to the "Ok" whenever Tag property is empty.
At first, you should declare ContentPresenter to show any object in your Content property of Button control.
<Style x:Key="OkBtn" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Rectangle/>
<ContentPresenter Content="{Binding Path=Content, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Button}}}"></ContentPresenter>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Then, it is possible to set another Content by using code behind or binding:
By code behind:
okButton.Content="desirableText";
By binding:
<Button x:Name="OkButton" Style="{DynamicResource OKBtn}" Content="{Binding FooText}" />
private string fooText;
public string FooText
{
get { return fooText; }
set
{
fooText = value;
OnPropertyChanged("FooText");
}
}

Two selected tabs in tabcontroller

I'm having an issue with a TabControl where I manage (in some special cases) to get two tabs headers selected (only one body showing afaik), and I can't change the selected tab.
Selected tabs have bold header text.
In this image, "Ämnesinformation" and "R43" are both selected.
My application is structured as follows:
I have some views:
MainView: The main view, contains the TabControl which only contains one item in the image.
SubstanceTabsView: One of these for every tab in MainView.
SubstanceView and ClassificationView: the first is used for the "Ämnesinformation", of which there is only one per substance. The second can have multiple instances, like "R43", "R12" etc.
I also have some viewModels:
MainViewModel: The VM for the MainView.
SubstanceTabsViewModel: The VM for the SubstanceTabsView, contains a set of IViewModels
SubstanceViewModel, ClassificationViewModel: both implement IViewModel, are VMs for SubstanceView and ClassificationView
Some relevant xaml code:
Here's the tabcontrol in MainView.xaml
<TabControl SelectedItem="{Binding Path=SelectedTab}" ItemsSource="{Binding Path=Tabs}" >
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" >
</TextBlock>
<local:CrossButton Margin="3" Padding="0" Width="12" Command="{Binding CloseCommand}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Name="Border"
Margin="0,0,-4,0"
Background="{Binding Path=HeaderBackground}"
BorderBrush="#A0A0A0"
BorderThickness="1,1,1,1"
CornerRadius="3,10,0,0" >
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Panel.ZIndex" Value="100" />
<Setter TargetName="Border" Property="Background" Value="{Binding HeaderBackground}" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="Yellow" />
<Setter TargetName="Border" Property="BorderBrush" Value="Black" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
<Setter Property="Foreground" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<DataTemplate DataType="{x:Type localViewModels:SubstanceTabsViewModel}">
<localViews:SubstanceTabsView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
Here's how I control the connection between different views and viewmodels in SubstanceTabsView.xaml
<TabControl SelectedItem="{Binding Path=SelectedTab}" ItemsSource="{Binding Path=Tabs}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" />
<local:CrossButton Margin="3" Padding="0" Width="12" Command="{Binding CloseCommand}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.Resources>
<DataTemplate DataType="{x:Type localViewModels:ClassificationViewModel}">
<localViews:ClassificationView />
</DataTemplate>
<DataTemplate DataType="{x:Type localViewModels:SubstanceViewModel}">
<localViews:SubstanceView />
</DataTemplate>
</TabControl.Resources>
</TabControl>
Here's the code for SubstanceTabsViewModel.cs which controls the second level tabs, the setter for the selectedTab controls some logic which asks the user about changing from an unsaved tab:
private IViewModel selectedTab;
public IViewModel SelectedTab
{
get
{
return selectedTab;
}
set
{
MessageBoxResult rsltMessageBox = MessageBoxResult.Yes;
if (selectedTab != null && selectedTab.SaveNeeded() && selectedTab.Id != 0 && value != null && selectedTab is ClassificationViewModel)
{
rsltMessageBox = notifyUserService.Ask("Bedömning är ändrad men ej sparad vill du verkligen lämna fliken?", "Bedömning ändrad");
}
if (rsltMessageBox == MessageBoxResult.Yes)
{
selectedTab = value;
}
OnPropertyChanged("SelectedTab");
}
}
private ObservableCollection<IViewModel> tabs;
public ObservableCollection<IViewModel> Tabs
{
get
{
return tabs;
}
set
{
tabs = value;
OnPropertyChanged("Tabs");
}
}
Some things my investigations have resulted in: If I don't do the notifyUserService call (which results in a messagebox.show()), there is no problem, only one tab is selected. If I look at the SelectedItem of the TabControl, it is only one item, the item it "should" be in my situation.
I finally found someone else having a similar problem, as described here**, "Displaying a message box causes a nested message pump; which means that almost all processing resumes. Of course, we are in the middle of trying to change the selected item, so this can cause all sorts of out-of-order or reentrancy problems. This class of problems is difficult to fix, and we are not going to be able to fix this in our next release." So the problem was with using MessageBox:es in the selectedItem setter.
I guess using some clever workaround is the appropriate solution in this case.
** Update March 2022
The URL referenced by the original post is no longer valid. The content can now be found here: WPF TabControl bug

WPF - MenuItem missing Icon/Image

Im getting menuItem icon appearing only on last menuItem.
If i snoop the app only last menuItem has image in icon, while if i debug all MenuItems appear to have image in icon. Also if i add submenuItem the icon on menuItem dissapears once i open submenus and the last submenu gets the icon... Any idea? PS: also tooltips on menu item dont work.
Im using caliburn micro and fluent ribbon controls.
<ControlTemplate x:Key="dropDownButton">
<ef:DropDownButton Header="{Binding DisplayName}"
ItemsSource="{Binding Items}"
LargeIcon="{Binding LargeIconPath}"
cm:Message.Attach="ClickAction()"
ef:KeyTip.Keys="{Binding KeyTip}">
<ef:DropDownButton.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header"
Value="{Binding DisplayName}"/>
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding Path=IconPath}"/>
</Setter.Value>
</Setter>
<Setter Property="ItemsSource"
Value="{Binding Items}"/>
<Setter Property="cm:Message.Attach"
Value="ClickAction()"/>
<Setter Property="ef:KeyTip.Keys"
Value="{Binding KeyTip}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</Setter.Value>
</Setter>
</Style>
</ef:DropDownButton.ItemContainerStyle>
<ef:DropDownButton.ToolTip>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</ef:DropDownButton.ToolTip>
</ef:DropDownButton>
You are setting Icon property to an Image control in Style. Now, only one copy of Style is created and thus, only one copy of Image is created. Now, any control can have only one parent at a time. So, when it is assigned to last MenuItem, it is removed from previous MenuItem controls. To fix this, use Templates.
Instead of setting Header property, set HeaderTemplate:
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0"
Source="{Binding Path=IconPath}" />
<TextBlock Grid.Column="1"
Text="{Binding DisplayName}" />
</Grid>
</DataTemplate>
</Setter.Value>
</Setter>
I'm not sure of what properties are exposed by the control toolkit you are using. But, I'm sure they must have a template property.
After doing this, you don't need to set Icon property in style.
I successfully use the following entries in a ResourceDictionary:
<!-- Define non-shared image to avoid loss of menu icons -->
<Image x:Key="MenuIconImage" Height="16" Width="16" x:Shared="false">
<Image.Source>
<DrawingImage Drawing="{Binding Icon}" />
</Image.Source>
</Image>
<Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Header" Value="{Binding DisplayName />
<Setter Property="Icon" Value="{StaticResource MenuIconImage}" />
</Style>
Works like this:
<DataTemplate x:Key="MenuItemHeaderTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Image Grid.Column="0" Source="{Binding Path=IconPath}" />
<Label Grid.Column="1" Content="{Binding DisplayName}" />
</Grid>
</DataTemplate>
<ControlTemplate x:Key="dropDownButton">
<ef:DropDownButton Header="{Binding DisplayName}"
ItemsSource="{Binding Items}"
LargeIcon="{Binding LargeIconPath}"
cm:Message.Attach="ClickAction()"
ef:KeyTip.Keys="{Binding KeyTip}">
<ef:DropDownButton.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="HeaderTemplate" Value="{StaticResource MenuItemHeaderTemplate}" />
<Setter Property="ItemsSource"
Value="{Binding Items}"/>
<Setter Property="cm:Message.Attach"
Value="ClickAction()"/>
<Setter Property="ef:KeyTip.Keys"
Value="{Binding KeyTip}"/>
<Setter Property="ToolTip">
<Setter.Value>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</Setter.Value>
</Setter>
</Style>
</ef:DropDownButton.ItemContainerStyle>
<ef:DropDownButton.ToolTip>
<ef:ScreenTip Title="{Binding DisplayName}"
HelpTopic="ScreenTip help ..."
Image="{Binding LargeIconPath}"
Text="Text for ScreenTip"/>
</ef:DropDownButton.ToolTip>
</ef:DropDownButton>
For some reason approach when Image is static resource with x:Shared = false doesn't work for me. Only last menu item shows icon. I've tried both StaticResource and DynamicResource. Here is my solution:
public class MenuItemIconHelper
{
#region ImageSource Icon
public static readonly DependencyProperty IconProperty = DependencyProperty.RegisterAttached("Icon", typeof(ImageSource), typeof(MenuItemIconHelper), new PropertyMetadata(default(ImageSource), IconPropertyChangedCallback));
private static void IconPropertyChangedCallback(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var i = (MenuItem)obj;
if (e.NewValue != null)
i.Icon = new Image() {Source = (ImageSource)e.NewValue};
else
i.Icon = null;
}
public static void SetIcon(DependencyObject element, ImageSource value)
{
element.SetValue(IconProperty, value);
}
public static ImageSource GetIcon(DependencyObject element)
{
return (ImageSource)element.GetValue(IconProperty);
}
#endregion
}
Sample:
<Style x:Key="CommandMenuItemStyle" TargetType="MenuItem">
<Setter Property="cb:MenuItemIconHelper.Icon" Value="car1.png" />
<Setter Property="Header" Value="{Binding Name}" />
I consider it to be more readable than using resource and you don't need to change MenuItem's HeaderTemplate. You can also implement some caching mechanism for ImageSource or Image.
1. Add Existing File... image file to resources (if you already have one, skip it).
2. In Solution Explorer select this image file.
3. Change Build Action to Resource.
And finally, you can add this image to XAML with simple call:
<Window.Resources>
<ContextMenu x:Key="contextMenu" >
<MenuItem Header="Restart" Name="menuItemRestart" Click="MenuItem_Click">
<MenuItem.Icon>
<Image Source="/Resources/restart.png"/>
</MenuItem.Icon>
</MenuItem>
<Separator/>
<MenuItem Header="Exit" Name="menuItemExit" Click="MenuItem_Click">
<MenuItem.Icon>
<Image Source="/Resources/window_close.png"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Window.Resources>
The Result:

Categories