WPF Bind in Button's ControlTemplate to property of CommandParameter? - wpf

I am trying to create a generic page navigation button using a Style and ControlTemplate. The idea is that the destination page is the CommandParameter of the button:
<Button Height="64" Width="64" Style="{DynamicResource ButtonViewTileStyle}"
Command="{Binding Path=ChangePage}"
CommandParameter="{Binding Path=HomePage}"/>
...and every page has a DisplayName and IconName property that I would like to use to use in the ControlTemplate. Here is a non-working example with just the display name:
<Style x:Key="ButtonViewTileStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Canvas>
<TextBlock TextWrapping="Wrap"
Text="{Binding CommandParameter.DisplayName,
RelativeSource={RelativeSource TemplatedParent}}"
Canvas.Left="0" Canvas.Top="0"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Any suggestions on what the Text= binding should be to make this work?

Please refer the below code.
<Window.Resources>
<Style x:Key="ButtonViewTileStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Canvas>
<TextBlock TextWrapping="Wrap"
Text="{Binding CommandParameter.DisplayName,
RelativeSource={RelativeSource TemplatedParent}}"
Canvas.Left="0" Canvas.Top="0"
HorizontalAlignment="Center" VerticalAlignment="Center"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<Button Command="{Binding TestCommand}" Width="100" Height="25" Style="{DynamicResource ButtonViewTileStyle}" CommandParameter="{Binding Path=PageDetails}"></Button>
</StackPanel>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new TestViewModel();
}
}
class TestViewModel
{
private CommndPrm pageDetials;
public CommndPrm PageDetails
{
get { return pageDetials; }
set { pageDetials = value; }
}
public TestViewModel()
{
pageDetials = new CommndPrm() { DisplayName="Test"};
}
}
class CommndPrm
{
private string displayName;
public string DisplayName
{
get { return displayName; }
set { displayName = value; }
}
}

Related

How to access XAML button from code behind WPF

My code:
<DataGrid HorizontalAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Hidden" BorderBrush="#83D744" IsSynchronizedWithCurrentItem="False" VerticalGridLinesBrush="Transparent" Grid.Column="0" RowHeaderWidth="0" CanUserAddRows="False" AutoGenerateColumns="False" x:Name="datagrid1" Margin="10,150,8,50" Background="Transparent" RowBackground="#FF494949" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" ItemsSource="{Binding}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#83D744"/>
<Setter Property="Opacity" Value="1"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="Height" Value="50"/>
</Style>
<Style x:Key="TextInCellCenter" TargetType="{x:Type TextBlock}" >
<Setter Property="TextAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="RightAligElementStyle">
<Setter Property="TextAlignment" Value="Right"/>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="LeftAligElementStyle">
<Setter Property="TextAlignment" Value="Left"/>
</Style>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Transparent"/>
</DataGrid.Resources>
<DataGrid.Columns >
<DataGridTextColumn Binding="{Binding ProductName}" ElementStyle="{StaticResource LeftAligElementStyle}" Header="NAZIV ARTIKLA" MinWidth="350" Foreground="White" FontSize="20" FontFamily="Verdana" />
<DataGridTextColumn Binding="{Binding Quantity}" ElementStyle="{StaticResource TextInCellCenter}" Header="KOLIČINA" MinWidth="200" Foreground="White" FontSize="20" FontFamily="Verdana" />
</DataGrid.Columns>
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="Black" Opacity="0.7">
<Expander.Header >
<DockPanel Height="50" Margin="0,0,0,0" Name="dockPanel" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}, Path=ActualWidth}">
<Button Name="btnFinishOrder" Content="Finish Order" Margin="0,0,55,5" DockPanel.Dock="Right" Click="btnFinishOrder_Click" FontSize="12" BorderThickness="1.5" HorizontalAlignment="Left" VerticalAlignment="Bottom" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Foreground="#83D744" Background="Transparent" BorderBrush="#83D744" Width="130" Height="40">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush= "{TemplateBinding BorderBrush}"
Background= "{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
<Button Name="btnTakeIt" Click="btnTakeIt_Click" Content="Take it" Margin="0,0,20,5" DockPanel.Dock="Right" FontSize="12" BorderThickness="1.5" HorizontalAlignment="Left" VerticalAlignment="Bottom" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Foreground="#83D744" Background="Transparent" BorderBrush="#83D744" Width="130" Height="40">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
<TextBlock FontWeight="Normal" FontFamily="Verdana" FontSize="20" Height="25" Foreground="#83D744" Text="{Binding Path=Name,StringFormat= Number of Order:# {0}}" />
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
There you can see guys, when I click on button Click="btnTakeIt_Click" I programaticaly change buttons text to "Order in progress." and I update field in my database IsInProgres to 1 - true, code is here:
private void btnTakeIt_Click(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
CollectionViewGroup group = b.DataContext as CollectionViewGroup;
var x = group.Name;
int orderNumber = Convert.ToInt32(x);
b.BorderBrush = null;
b.Content = "Order is in progress";
b.FontSize = 12;
OrdersController.SetOrderInProgressByID(orderNumber);
}
But what is happening, because I'm refreshing my grid every 20 seconds my button content is becoming again like it's on default "Take it!" because that's whats written in XAML.
Like this:
This is my code behind (refresh every few seconds):
public MainWindow()
{
try
{
InitializeComponent();
this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
this.WindowState = WindowState.Maximized;
var ordersList = OrdersController.localOrders();
collectionViewSource.Source = ordersList;
collectionViewSource.GroupDescriptions.Add(new PropertyGroupDescription("NumberOfOrder"));
DataContext = collectionViewSource;
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(20);
timer.Tick += timer_Tick;
timer.Start();
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}
void timer_Tick(object sender, EventArgs e)
{
var ordersList = OrdersController.localOrders();
collectionViewSource.Source = null;
collectionViewSource.Source = ordersList;
DataContext = collectionViewSource;
datagrid1.ScrollIntoView(datagrid1.Items[datagrid1.Items.Count - 1]);
}
}
So my question is could I somehow loop my localOrders and check if NumberOfOrder has IsInProgress = 1 and simply set this:
btnTakeIt.Content="Order in progress.."
So everytime my grid refresh I can loop my orders, and check is order InProgress and after that I can set button btnTakeIt content to "Order in progress.."
Or if there is any other way, I am opened to try it!
P.S I tried allready but my btnTakeIt is not accessible in code behind :(
EDIT:
void timer_Tick(object sender, EventArgs e)
{
Button MyButton = FindChild<Button>(datagrid1, "btnTakeIt");
var ordersList = OrdersController.localOrders();
collectionViewSource.Source = null;
collectionViewSource.Source = ordersList;
foreach (var item in ordersList)
{
if (item.IsInProgress== true)
{
MyButton.Content = "Order is in progress";
}
}
DataContext = collectionViewSource;
}
Can I do it like this? I checked debuger and it is entering inside of if statement and when I look at breakpoint it acctualy changes content but on grid I can not see anything changed :(
EDIT:
I have to mention that my class Product is contained in my OrdersController :)
#Ayuman
what do you think about this:
<Button Name="btnTakeIt" DataContext="{Binding Items[0]}" Content="{Binding Status}"Click="btnTakeIt_Click" Content="Take it" Margin="0,0,20,5" DockPanel.Dock="Right" FontSize="12" BorderThickness="1.5" HorizontalAlignment="Left" VerticalAlignment="Bottom" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Foreground="#83D744" Background="Transparent" BorderBrush="#83D744" Width="130" Height="40">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
code behind:
public static List<LocalOrders> localOrders()
{
var results = DataServices.POS.proc_GetAllOrders().ToList();
List<LocalOrders> localOrdersList = new List<LocalOrders>();
foreach (var item in results)
{
LocalOrders lokal = new LocalOrders();
if (item.IsInProgress)
{
localo.Pihvacena = true;
localo.Status = "IN PROCESS";
}
else
{
lokalne.Status = "IT IS NOT YET IN PROCESS";
}
lokal.User = item.User;
lokal.Quantity = Convert.ToInt32(item.Quantity);
lokal.Title = item.Title;
lokal.NumberOfOrder = item.NumberOfOrder;
localOrdersList.Add(lokal);
}
return localOrdersList;
}
So I can programaticaly check for status of order and set content of button like that.. is it good solution maybe?
Please refer below code. I tried to make it work. Hope this works for you as well
<DataGrid HorizontalAlignment="Stretch" ScrollViewer.HorizontalScrollBarVisibility="Hidden" BorderBrush="#83D744" IsSynchronizedWithCurrentItem="False" VerticalGridLinesBrush="Transparent" Grid.Column="0" RowHeaderWidth="0" CanUserAddRows="False" AutoGenerateColumns="False" x:Name="datagrid1" Margin="10,150,8,50" Background="Transparent" RowBackground="#FF494949" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" ItemsSource="{Binding}">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Background" Value="#83D744"/>
<Setter Property="Opacity" Value="1"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="HorizontalContentAlignment" Value="Center" />
<Setter Property="FontSize" Value="18"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="Height" Value="50"/>
</Style>
<Style x:Key="TextInCellCenter" TargetType="{x:Type TextBlock}" >
<Setter Property="TextAlignment" Value="Center"/>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="RightAligElementStyle">
<Setter Property="TextAlignment" Value="Right"/>
</Style>
<Style TargetType="{x:Type TextBlock}" x:Key="LeftAligElementStyle">
<Setter Property="TextAlignment" Value="Left"/>
</Style>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Transparent"/>
</DataGrid.Resources>
<DataGrid.Columns >
<DataGridTextColumn Binding="{Binding ProductName}" ElementStyle="{StaticResource LeftAligElementStyle}" Header="NAZIV ARTIKLA" MinWidth="350" Foreground="White" FontSize="20" FontFamily="Verdana" />
<DataGridTextColumn Binding="{Binding Quantity}" ElementStyle="{StaticResource TextInCellCenter}" Header="KOLIČINA" MinWidth="200" Foreground="White" FontSize="20" FontFamily="Verdana" />
</DataGrid.Columns>
<DataGrid.GroupStyle>
<!-- Style for groups at top level. -->
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" Background="Black" Opacity="0.7">
<Expander.Header >
<DockPanel Height="50" Margin="0,0,0,0" Name="dockPanel" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}, Path=ActualWidth}">
<Button Name="btnFinishOrder" DataContext="{Binding Items[0]}" Content="{Binding ButtonCaption}" Margin="0,0,55,5" DockPanel.Dock="Right" Click="BtnFinishOrder_OnClick" FontSize="12" BorderThickness="1.5" HorizontalAlignment="Left" VerticalAlignment="Bottom" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Foreground="#83D744" Background="Transparent" BorderBrush="#83D744" Width="130" Height="40">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush= "{TemplateBinding BorderBrush}"
Background= "{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
<Button Name="btnTakeIt" Click="BtnTakeIt_OnClick" DataContext="{Binding Items[0]}" Content="{Binding ButtonCaption}" Margin="0,0,20,5" DockPanel.Dock="Right" FontSize="12" BorderThickness="1.5" HorizontalAlignment="Left" VerticalAlignment="Bottom" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" Foreground="#83D744" Background="Transparent" BorderBrush="#83D744" Width="130" Height="40">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
<TextBlock FontWeight="Normal" FontFamily="Verdana" FontSize="20" Height="25" Foreground="#83D744" Text="{Binding Items[0].ProductName}" />
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
public partial class MainWindow : Window
{
private ICollectionView view;
private ObservableCollection<Product> ordersList;
public MainWindow()
{
InitializeComponent();
this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
this.WindowState = WindowState.Maximized;
ordersList = new ObservableCollection<Product>()
{
new Product()
{
ProductName = "Prodct",
Quantity = 1,
NumberOfOrder = 100,
IsInProgress = true,
ButtonCaption = "Take it"
},
new Product()
{
ProductName = "Prodct1",
Quantity = 2,
NumberOfOrder = 1000,
ButtonCaption = "Take it"
},
new Product()
{
ProductName = "Prodct2",
Quantity = 3,
NumberOfOrder = 10000,
ButtonCaption = "Take it"
},
new Product()
{
ProductName = "Prodct3",
Quantity = 4,
NumberOfOrder = 100000,
ButtonCaption = "Take it"
},
};
view = CollectionViewSource.GetDefaultView(ordersList);
view.GroupDescriptions.Add(new PropertyGroupDescription("NumberOfOrder"));
DataContext = view;
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(20);
timer.Tick += Timer_Tick; ;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
foreach (var item in ordersList)
{
if (item.IsInProgress)
{
item.ButtonCaption = "Order is Still in progress";
}
}
}
private void BtnFinishOrder_OnClick(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
private void BtnTakeIt_OnClick(object sender, RoutedEventArgs e)
{
Button b = sender as Button;
Product prod = b.DataContext as Product;
b.BorderBrush = null;
prod.ButtonCaption = "Order is in progress";
b.FontSize = 12;
}
}
class Product:INotifyPropertyChanged
{
private string productName;
public string ProductName
{
get { return productName ; }
set
{
productName = value ;
OnPropertyChanged("ProductName");
}
}
private int quantity;
public int Quantity
{
get { return quantity; }
set
{
quantity = value;
OnPropertyChanged("Quantity");
}
}
public int NumberOfOrder { get; set; }
public bool IsInProgress { get; set; }
private string buttonCaption;
public string ButtonCaption
{
get { return buttonCaption; }
set
{
buttonCaption = value;
OnPropertyChanged("ButtonCaption");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}

WPF ComboBox Multiple Columns

I am just wondering if there is a wpf combobox control that can contain multiple columns?
And if not, what XAML I need to use to achieve this?
I am just looking for a basic two column combobox if is possible,
Thanks
Please Refer these links for Multiple Column Combobox which is implemented by editing combox and comboboxitem Default template/style.
1)Link1
2)Link2
Xaml code : Please take a look at commented Trigger IsHighlighted in ComboboxItem style
<Grid>
<ComboBox Height="30" Margin="5" ItemsSource="{Binding}" HorizontalContentAlignment="Stretch">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Margin="2" Text="{Binding Name}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid x:Name="gd" TextElement.Foreground="Black">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Column="0" Text="{Binding Name}"/>
<TextBlock Margin="5" Grid.Column="1" Text="{Binding State}"/>
<TextBlock Margin="5" Grid.Column="2" Text="{Binding Population}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ComboBoxItem.IsSelected" Value="True">
<Setter TargetName="gd" Property="Background" Value="Gray"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
<Trigger Property="ComboBoxItem.IsMouseOver" Value="True">
<Setter TargetName="gd" Property="Background" Value="Blue"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
<!--IsHighlighted and IsMouseOver is showing same effect but IsHighlighted is used for showing logical focus( for understanding check using tab key)-->
<!--<Trigger Property="ComboBoxItem.IsHighlighted" Value="True">
<Setter TargetName="gd" Property="Background" Value="Yellow"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="Black"></Setter>
</Trigger>-->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
c# code
public partial class MainWindow : Window
{
private ObservableCollection<City> cities = new ObservableCollection<City>();
public MainWindow()
{
InitializeComponent();
cities.Add(new City() { Name = "Mumbai", State = "Maharashtra", Population = 3000000 });
cities.Add(new City() { Name = "Pune", State = "Maharashtra", Population = 7000000 });
cities.Add(new City() { Name = "Nashik", State = "Maharashtra", Population = 65000 });
cities.Add(new City() { Name = "Aurangabad", State = "Maharashtra", Population = 5000000 });
DataContext = cities;
}
}
class City
{
public string State { get; set; }
public string Name { get; set; }
public int Population { get; set; }
}
Output
Because I found, Heena, that your Xaml does not provide selected dropped down items to be highlighted I modified your code as follows:
Xaml
<ComboBox Name="cbCities" Height="30" Margin="5" HorizontalContentAlignment="Left" HorizontalAlignment="Stretch" ItemsSource="{Binding}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="2" Text="{Binding Name}"/>
<TextBlock Margin="2" Text="{Binding State}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ComboBoxItem}">
<Border Name="templateBorder" Padding="2" SnapsToDevicePixels="true">
<ContentPresenter>
<ContentPresenter.Content>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Margin="5" Grid.Column="0" Text="{Binding Name}"/>
<TextBlock Margin="5" Grid.Column="1" Text="{Binding State}"/>
<TextBlock Margin="5" Grid.Column="2" Text="{Binding Population}"/>
</Grid>
</ContentPresenter.Content>
</ContentPresenter>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter Property="Foreground" Value="{x:Static SystemColors.HighlightTextBrush}"/>
<Setter TargetName="templateBorder" Property="Background" Value="{x:Static SystemColors.HighlightBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
C#
private void Window_Loaded(object sender, RoutedEventArgs e)
{
cities.Add(new City() { Name = "Boston", State = "MA", Population = 3000000 });
cities.Add(new City() { Name = "Los Angeles", State = "CA", Population = 7000000 });
cities.Add(new City() { Name = "Frederick", State = "MD", Population = 65000 });
cities.Add(new City() { Name = "Houston", State = "TX", Population = 5000000 });
cbCities.DataContext = cities;
}
class City
{
public string State { get; set; }
public string Name { get; set; }
public int Population { get; set; }
}
Output
I know im late but this is how you do it in a simplified way, After the DataTemplate tag you can put anything depending on how you want your lay out to look like.
<ComboBox.ItemTemplate>
<DataTemplate >
<StackPanel Orientation="Horizontal">
<TextBlock Foreground="{StaticResource ForegroundMainBrush}"
Margin="5 0"
FontFamily="{StaticResource LatoBold}"
VerticalAlignment="Center">
<Run Text="Code :" />
<Run Text="{Binding ActivityCode,Mode=OneWay}" />
</TextBlock>
<TextBlock Foreground="{StaticResource ForegroundDarkBrush}"
Margin="5 0"
Text="|"
FontFamily="{StaticResource LatoBold}"
VerticalAlignment="Center" />
<TextBlock Foreground="{StaticResource ForegroundMainBrush}"
Margin="5 0"
FontFamily="{StaticResource LatoBold}"
VerticalAlignment="Center">
<Run Text="Rate :" />
<Run Text="{Binding Rate,Mode=OneWay}" />
</TextBlock>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
Result
Sample Result
Or Use a readonly property in your DataModel as shown on the code below and set your combobox DisplayMemberPath to DisplayMemberPath="CodeRate"
public string ActivityCode { get; set; }
public string Rate { get; set; }
public string CodeRate => string.Format("Code: {0} | Rate:
{1}",ActivityCode,Rate);
I just use StackPanels in mine. Maybe kind of redundant per item, but it got me exactly the solution I wanted without getting too deep into things.
<ComboBox Name="cboTask">
<StackPanel Orientation="Horizontal">
<ComboBoxItem Name="someTask" Content="Doing Some Task" /><Button Name="cmdDetails" Content="..." />
</StackPanel>
</ComboBox>

Silverlight Tooltip Style ContentPresenter TextWrapping

I want to create a Silverlight Tooltip Style but I don't want to use a textblock because the tooltip content might be an image or something else. So I'm using a ContentPresenter. My problem is how to set MaxWidth and force TextWrapping when content is text. This is what I have so far:
<ResourceDictionary
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:vsm="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit">
<Style TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border BorderBrush="DimGray" BorderThickness="1" CornerRadius="5" Background="WhiteSmoke" Opacity="0.8"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,10,0,-5" Width="Auto">
<Border.Effect>
<DropShadowEffect BlurRadius="16" ShadowDepth="8" Direction="-45" Color="Black" Opacity="0.6"/>
</Border.Effect>
<Grid>
<ContentPresenter Content="{TemplateBinding Content}" Margin="3"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
I found one way to do this:
Create 2 Tooltip styles - one for text and another for everything else, like this:
<Style x:Key="TooltipStyleForText" TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border BorderBrush="DimGray" BorderThickness="1" CornerRadius="5" Background="WhiteSmoke" Opacity="0.8"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,10,0,-5" Width="Auto">
<Border.Effect>
<DropShadowEffect BlurRadius="16" ShadowDepth="8" Direction="-45" Color="Black" Opacity="0.6"/>
</Border.Effect>
<Grid>
<TextBlock Text="{TemplateBinding Content}" MaxWidth="400" TextWrapping="Wrap" Margin="3"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TooltipStyleForOtherStuffThanText" TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border BorderBrush="DimGray" BorderThickness="1" CornerRadius="5" Background="WhiteSmoke" Opacity="0.8"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,10,0,-5" Width="Auto">
<Border.Effect>
<DropShadowEffect BlurRadius="16" ShadowDepth="8" Direction="-45" Color="Black" Opacity="0.6"/>
</Border.Effect>
<Grid>
<ContentPresenter Content="{TemplateBinding Content}" Margin="3"/>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and than when we use a TextBlock, call it's ToolTip style like this:
<TextBlock Text="something">
<ToolTipService.ToolTip>
<ToolTip Style="{StaticResource TooltipStyleForText}">
<TextBlock Text="Some text"/>
</ToolTip>
</ToolTipService.ToolTip>
</TextBlock>
This might not be the best solution because we have two styles so we need to specify write several lines for a simple TextBlock.
I hope someone has a better solution.
We needed to do the same thing and tried a few approaches before setting on the following solution. The problem is that when the ToolTip.Content is a string you need to show the tooltip using a TextBlock. When ToolTip.Content is not a string you need to use a ContentPresenter. So we ended up defining a ControlTemplate that has both, and we show/hide the appropriate one based on the type of ToolTip.Content. This approach is applied to all ToolTips in the application:
The following converter class helps out with this:
public class TooltipContentVisibilityConverter : IValueConverter
{
public Visibility VisibilityWhenToolTipContentIsAString { get; set; }
public Visibility VisibilityWhenToolTipContentIsNotAString { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var toolTip = value as ToolTip;
if (toolTip != null && toolTip.Content is string)
{
return VisibilityWhenToolTipContentIsAString;
}
return VisibilityWhenToolTipContentIsNotAString;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Then in your App.xaml you define the following.
<Converter:TooltipContentVisibilityConverter VisibilityWhenToolTipContentIsAString="Visible" VisibilityWhenToolTipContentIsNotAString="Collapsed" x:Key="tooltipStringContentVisibilityConverter" />
<Converter:TooltipContentVisibilityConverter VisibilityWhenToolTipContentIsAString="Collapsed" VisibilityWhenToolTipContentIsNotAString="Visible" x:Key="tooltipNonStringContentVisibilityConverter" />
<Style TargetType="ToolTip">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<Border CornerRadius="0" Background="{TemplateBinding Background}" BorderBrush="Black" BorderThickness="1" Padding="4">
<StackPanel Orientation="Vertical">
<TextBlock Text="{TemplateBinding Content}" TextWrapping="Wrap" MaxWidth="300"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=ToolTip},Converter={StaticResource tooltipStringContentVisibilityConverter}}"/>
<ContentPresenter Content="{TemplateBinding Content}"
Visibility="{Binding RelativeSource={RelativeSource AncestorType=ToolTip},Converter={StaticResource tooltipNonStringContentVisibilityConverter}}"/>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
It seems like a bit of an awkward way to have to solve this problem, but it does work and we haven't found a better solution yet.

Can we have buttons in a validation template, and how can we bind it to the ViewModel method?

Requirement:
Need to display an error message when the user types a forlder name that doesn't exist as shown below:
Problem: I am able to display the UI but not able to call a method in the view model when the user clicks on the button "CreateNew"
View Model Code:
public string this[string columnName]
{
get { return "The entered folder name doesn't exist."; }
}
RelayCommand createNewFolder;
public RelayCommand CreateNewFolder
{
get
{
if (createNewFolder == null)
createNewFolder = new RelayCommand(param => this.OnCreateNewFolder());
return createNewFolder;
}
}
public void OnCreateNewFolder()
{
MessageBox.Show("CreateNewFolder");
}
RelayCommand.cs can be downloaded at: http://code.msdn.microsoft.com/Project/Download/FileDownload.aspx?ProjectName=mag200902MVVM&DownloadId=4357
Xaml Code:
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<DockPanel LastChildFill="True">
<Border Margin="5,5,0,0" DockPanel.Dock="Bottom" Background="Red">
<StackPanel>
<TextBlock Name="ErrorText" Foreground="White" Background="Red"
FontSize="12" Padding="2" FontFamily="Trebuchet MS"
TextWrapping="Wrap"
Text="{Binding [0].ErrorContent}" ></TextBlock>
<StackPanel Margin="0" Orientation="Horizontal">
<Button Content="Create New" Command="{Binding Path=CreateNewFolder}" Margin="10" Padding="5"></Button>
<Button Content="Cancel" Margin="10" Padding="5" ></Button>
</StackPanel>
</StackPanel>
</Border>
<AdornedElementPlaceholder Name="ErrorTextBox" />
</DockPanel>
</ControlTemplate>
<Style x:Key="ValidationStyle" TargetType="{x:Type ComboBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BitmapEffect">
<Setter.Value>
<BitmapEffectGroup>
<OuterGlowBitmapEffect GlowColor="Red" GlowSize="3" Noise="0.6"></OuterGlowBitmapEffect>
</BitmapEffectGroup>
</Setter.Value>
</Setter>
<Setter Property="DataContext"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},Path=DataContext}">
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<AdornerDecorator >
<ComboBox IsEditable="True" FontSize="11" Margin="10" Width="250"
VerticalAlignment="Center"
Text="{Binding Path=StrText, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource ValidationStyle}"></ComboBox>
</AdornerDecorator>
</Grid>
Please note that i set the DataContext property in the style:
<Setter Property="DataContext" Value="{Binding RelativeSource={x:Static
RelativeSource.Self},Path=DataContext}">
</Setter>
Please let me know how to bind the method to a button in the validation template.
You could reference the DataContext for the AdornerDecorators Child in the Binding. I think something like this will work
<Button Content="Create New"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type AdornerDecorator}},
Path=Child.DataContext.CreateNewFolder}"
Margin="10" Padding="5"></Button>
This line looks suspicious:
createNewFolder = new RelayCommand(param => this.OnCreateNewFolder());
Maybe you should replace it by:
createNewFolder = new RelayCommand(OnCreateNewFolder);

Data Template not injected in ItemContainer

I have an ItemsControl class that overrides the following methods:
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is TilePanelItem;
}
protected override DependencyObject GetContainerForItemOverride()
{
return new TilePanelItem();
}
I provided the template for TilePanelItem which is the container of ItemsControl:
<ControlTemplate x:Key="tileItemTemplate" TargetType="my:TilePanelItem">
<Grid Width="200" Height="100">
<Border BorderBrush="Black" BorderThickness="2">
<ContentPresenter RenderTransformOrigin=".5,.5" />
</Border>
</Grid>
</ControlTemplate>
And the Style for it:
<Style TargetType="my:TilePanelItem">
<Setter Property="Template" Value="{StaticResource tileItemTemplate}" />
</Style>
And finally the instance of my ItemsControl with the DataTemplate:
<my:TileItemsControl x:Name="tileControl" ItemsSource="{Binding}" >
<my:TileItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</my:TileItemsControl.ItemsPanel>
<my:TileItemsControl.ItemTemplate>
<DataTemplate>
<Border Background="Blue">
<TextBlock Text="here I am" />
</Border>
</DataTemplate>
</my:TileItemsControl.ItemTemplate>
</my:TileItemsControl>
The DataTemplate that I defined is not injected in ItemContainer. When I run the app, it shows the borders as I declared in ItemContainter template but I don't see the DataTemplate.
What am I missing?
Thanks a million
You need to bring the ItemsTemplate in to become the ContentPresenter's ContentTemplate:
<ControlTemplate x:Key="tileItemTemplate" TargetType="my:TilePanelItem">
<Grid Width="200" Height="100">
<Border BorderBrush="Black" BorderThickness="2">
<ContentPresenter RenderTransformOrigin=".5,.5"
ContentTemplate="{Binding ItemsTemplate, RelativeSource={RelativeSource FindAncestor,my:TileItemsControl,1}}" />
</Border>
</Grid>
</ControlTemplate>

Resources