I am trying to figure out how to get data triggers to work between user controls - either between a window and a child user control (a user control embedded in the window), or between a user control that has a child user control.
The button control has 5 buttons but by default the 5th button is collapsed. When the combobox item "Fifth Button" is selected I want the Fourth button to collapse and the Fifth button to become visible. As you can see I have the triggers set to update the Label on the Mainwindow based on the combobox selection. I have no issue using triggers within the same window but I don't know how to make them work to communicate to a user control that is embedded in the same window. Or from one control to another.
<Window x:Class="ComboboxControlChange.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ComboboxControlChange"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<ComboBox Grid.Row="0" x:Name="ButtonSelectCombobox" SelectedValuePath="Content" SelectedValue="{Binding ButtonSelection}" Height="24" Margin="150,0">
<ComboBoxItem x:Name="FirstButtonSelection" >First Button</ComboBoxItem>
<ComboBoxItem x:Name="SecondButtonSelection">Second Button</ComboBoxItem>
<ComboBoxItem x:Name="ThirdButtonSelection">Third Button</ComboBoxItem>
<ComboBoxItem x:Name="FourthButtonSelection">Fourth Button</ComboBoxItem>
<ComboBoxItem x:Name="FifthButtonSelection">Fifth Button</ComboBoxItem>
</ComboBox>
<StackPanel Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center" Orientation="Vertical">
<Label>You have selected button:</Label>
<Label HorizontalAlignment="Center">
<Label.Style>
<Style TargetType="{x:Type Label}">
<Setter Property="Content" Value=""/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=FirstButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="One" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=SecondButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="Two" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=ThirdButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="Three" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=FourthButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="Four" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=FifthButtonSelection, Path=IsSelected}" Value="true">
<Setter Property="Content" Value="Five" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</StackPanel>
</Grid>
</Grid>
<Grid Grid.Row="1">
<local:ButtonControl />
</Grid>
</Grid>
</Window>
<UserControl x:Class="ComboboxControlChange.ButtonControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ComboboxControlChange"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="517">
<Grid Name="Link1MainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Column="0" VerticalAlignment="Center" Margin="5,0" >
<TextBlock TextAlignment="Center">
First<LineBreak/>Button
</TextBlock>
</Button>
<Button Grid.Column="1" VerticalAlignment="Center" Margin="5,0">
<TextBlock TextAlignment="Center">
Second<LineBreak/>Button
</TextBlock>
</Button>
<Button Grid.Column="2" VerticalAlignment="Center" Margin="5,0">
<TextBlock TextAlignment="Center">
Third<LineBreak/>Button
</TextBlock>
</Button>
<Button Grid.Column="3" VerticalAlignment="Center" Margin="5,0" >
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=FifthButtonSelected, Path=IsSelected}" Value="true">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock TextAlignment="Center">
Fourth<LineBreak/>Button
</TextBlock>
</Button>
<Button Grid.Column="3" Margin="4,4,4,50" Visibility="Collapsed">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=FifthButtonSelected, Path=IsSelected}" Value="true">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock TextAlignment="Center">
Fifth<LineBreak/>Button
</TextBlock>
</Button>
</Grid>
</UserControl>
I've tried binding the buttons with ElementName, Path, and even relativeSource but have't had any success. I've also tried adding the triggers in the ButtonControl.Resources section of the control.
<DataTrigger Binding="{Binding ElementName=FifthButtonSelected, Path=IsSelected}" Value="true">
<DataTrigger Binding="{Binding RelativeSource={RelatvieSource FindAncestorType={x:Type ComboBoxItem}}, Path=IsSelected}" Value="true">
Any help would be appreciated!
This won't work because the element names that are in the window will not be in scope for the user control. MSDN says:
[...] the primary XAML namescope is defined at the XAML root element of a
single XAML production, and encompasses the elements that are
contained in that XAML production.
What that means, practically, is that when you define an x:Name for an element in a file, it can only be referenced in that file, and will be unknown outside of it.
Another way to go about this would be to create a Dependency Property on the user control, and use that as a way to pass information between the window and the control. A nice side affect is that this creates some abstraction and allows for more flexibility.
ButtonControl.xaml.cs: (Rename 'Feature' to something relevant)
public partial class ButtonControl : UserControl
{
...
public bool IsFeatureVisible
{
get { return (bool)GetValue(IsFeatureVisibleProperty); }
set { SetValue(IsFeatureVisibleProperty, value); }
}
// Using a DependencyProperty as the backing store for IsFeatureVisible. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsFeatureVisibleProperty =
DependencyProperty.Register("IsFeatureVisible", typeof(bool), typeof(ButtonControl), new UIPropertyMetadata(false));
...
}
Now you could wire up the trigger to use this property, but we're lucky in this case that you're dealing with booleans and Visibility, so we can make it simpler:
ButtonControl.xaml:
<UserControl x:Class="ComboboxControlChange.ButtonControl"
x:Name="Myself"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:ComboboxControlChange"
mc:Ignorable="d"
d:DesignHeight="160" d:DesignWidth="517">
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="mBooleanToVisibilityConverter"/>
</UserControl.Resources>
<Grid Name="Link1MainGrid">
...
<Button Grid.Column="3" VerticalAlignment="Center" Margin="5,0" Visibility="{Binding ElementName=Myself, Path=IsFeatureVisible, Converter={StaticResource mBooleanToVisibilityConverter}}">
<TextBlock TextAlignment="Center">
Fourth<LineBreak/>Button
</TextBlock>
</Button>
...
</Grid>
</UserControl>
Lastly, in the Window, we need to provide a value for that property on the instance of our button control. We can use the FifthButtonSelection element name here just like you are in other parts of the file:
MainWindow.xaml
<local:ButtonControl IsFeatureVisible="{Binding ElementName=FifthButtonSelection, Path=IsSelected}"/>
Related
I'm trying to load a Grid with UIElements and/or FrameworkElements that have Style and DataTriggers.
The following is a simple native XAML which works as expected; toggling the check box through the 3 states give three different styles to the Rectangle (see image at bottom).
<Grid
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:XamlVsLoad"
mc:Ignorable="d"
Height="450" Width="800">
<CheckBox Name="MyCheck" IsChecked="True" Content="Checkbox" IsThreeState="True" Margin="10,10,0,0" Width="200"/>
<Rectangle Height="100" Width="100" StrokeThickness="5" Margin="10,50,100,100">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Fill" Value="White"/>
<Setter Property="Stroke" Value="Black"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyCheck, Path=IsChecked}" Value="True" >
<Setter Property="Fill" Value="Orange"/>
<Setter Property="Stroke" Value="Blue"/>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=MyCheck, Path=IsChecked}" Value="False" >
<Setter Property="Fill" Value="Blue"/>
<Setter Property="Stroke" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</Grid>
It also works when I save the same text to a file and load it dynamically into Window's Viewboxs with XAML like this:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<DockPanel Grid.Column="0">
<DockPanel>
<Label Content="Native" DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="10,10,10,10" />
<Viewbox Name="NativeViewbox" Height="auto" Width="200" DockPanel.Dock="Top"/>
<!-- Native Grid Omitted for brevity 3 uniquely named checkboxes -->
</Viewbox>
</DockPanel>
<DockPanel Grid.Column="1">
<DockPanel>
<Label Content="Dynamic" DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="10,10,10,10" />
<Viewbox Name="DynamicViewbox" Height="auto" Width="200" DockPanel.Dock="Top"/>
</DockPanel>
</DockPanel>
<DockPanel Grid.Column="2">
<DockPanel>
<Label Content="Clone" DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="10,10,10,10" />
<Viewbox Name="CloneViewbox" Height="auto" Width="200" DockPanel.Dock="Top" />
</DockPanel>
</DockPanel>
<DockPanel Grid.Column="3">
<DockPanel>
<Label Content="Saved" DockPanel.Dock="Top" HorizontalAlignment="Center" Margin="10,10,10,10" />
<Viewbox Name="SavedViewbox" Height="auto" Width="200" DockPanel.Dock="Top"/>
</DockPanel>
</DockPanel>
However, if I try to copy/clone/deepcopy (I've tried several different methods) the FrameworkElements/UIElements from one Grid/container to a new one, the style no longer works. Here are the various ways I'm currently loading and cloning them:
public partial class ReadCopy : Window {
public ReadCopy() {
InitializeComponent();
// test the dynamic load
Grid NativeXaml = ReadGrid("C:\\XamlFiles\\LoadXaml.xaml");
DynamicViewbox.Child = NativeXaml; // honors style
// test the Clone load
Grid CloneXaml = new Grid();
foreach (FrameworkElement fe in NativeXaml.Children) CloneXaml.Children.Add(Clone(fe));
CloneViewbox.Child = CloneXaml; // doesn't honor style
// test the save Clone and then load
StringBuilder outstr = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true, IndentChars = " ", NewLineChars = "\r\n", NewLineHandling = NewLineHandling.Replace };
XamlDesignerSerializationManager dsm = new XamlDesignerSerializationManager(XmlWriter.Create(outstr, settings)) { XamlWriterMode = XamlWriterMode.Expression };
XamlWriter.Save(CloneXaml, dsm);
File.WriteAllText("C:\\XamlFiles\\SavedXaml.xaml", outstr.ToString());
Grid SavedXaml = ReadGrid("C:\\XamlFiles\\SavedXaml.xaml");
SavedViewbox.Child = SavedXaml; // honors style and triggers again...
}
public Grid ReadGrid(string fn) {
FileStream fs = new FileStream(fn, FileMode.Open);
return XamlReader.Load(fs) as Grid;
}
public FrameworkElement Clone(FrameworkElement it) {
FrameworkElement clone;
using (var stream = new MemoryStream()) {
XamlWriter.Save(it, stream);
stream.Seek(0, SeekOrigin.Begin);
clone = (FrameworkElement)XamlReader.Load(stream);
}
clone.Style = it.Style; // setting it or not has no effect
return clone;
}
}
The output of the beginning simple example grid's cloned XAML via XamlWriter.Save:
<Grid xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<CheckBox IsChecked="True" IsThreeState="True" Style="{x:Null}" Name="MyCheck" Width="200" Margin="10,10,0,0">Checkbox</CheckBox>
<Rectangle StrokeThickness="5" Width="100" Height="100" Margin="10,50,100,100">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsChecked, ElementName=MyCheck}" Value="True">
<Setter Property="Shape.Fill">
<Setter.Value>
<SolidColorBrush>#FFFFA500</SolidColorBrush>
</Setter.Value>
</Setter>
<Setter Property="Shape.Stroke">
<Setter.Value>
<SolidColorBrush>#FF0000FF</SolidColorBrush>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsChecked, ElementName=MyCheck}" Value="False">
<Setter Property="Shape.Fill">
<Setter.Value>
<SolidColorBrush>#FF0000FF</SolidColorBrush>
</Setter.Value>
</Setter>
<Setter Property="Shape.Stroke">
<Setter.Value>
<SolidColorBrush>#FFFFA500</SolidColorBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
<Style.Resources>
<ResourceDictionary />
</Style.Resources>
<Setter Property="Shape.Fill">
<Setter.Value>
<SolidColorBrush>#FFFFFFFF</SolidColorBrush>
</Setter.Value>
</Setter>
<Setter Property="Shape.Stroke">
<Setter.Value>
<SolidColorBrush>#FF000000</SolidColorBrush>
</Setter.Value>
</Setter>
</Style>
</Rectangle.Style>
</Rectangle>
</Grid>
While I can see it reformatted the Style, I don't understand why it no longer works when I set the Viewbox.Child with the clone. What's even more confusing is that the loading the saved file of the clone then works. Here's what all four (Native, Dynamic, Cloned, Saved Clone Reloaded) look like:
Can anyone explain how to properly preserve the style through a copy/clone?
The Style on the Rectangle is triggered by the CheckBox. Because of this, Clone() will not work loading the child elements separately. To load them at the same time, clone their parent, the Grid itself. I reproduced your problem and this worked for me:
Grid NativeXaml = ReadGrid();
DynamicViewbox.Child = NativeXaml;
Grid CloneXaml = (Grid)Clone(NativeXaml);
CloneViewbox.Child = CloneXaml;
I am building a chat application in which i have below xaml code -
<Page
x:Class="MyProject1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
mc:Ignorable="d" Background="Black">
<Grid Background="White" Name="mainGrid">
<Border BorderBrush="Cyan" BorderThickness="0.2" Margin="3,0,3,3">
<ListView x:Name="ListView" VerticalAlignment="Bottom" SelectionMode="None" IsItemClickEnabled="True">
<ListView.ItemTemplate>
<DataTemplate>
<controls:MarkdownTextBlock Name="markdownBlock" Text="{Binding Text}" TextWrapping="Wrap" FontFamily="Segoe-UI">
</controls:MarkdownTextBlock>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="FontSize" Value="14" />
<Setter Property="Foreground" Value="Black" />
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Border>
</Grid>
</Page>
I use below code in my code behind to send text to the MarkdownTextBlock control
messages.Add(new Message() { Text = "**Person 1:** " + message });
and the response -
messages.Add(new Message() { Text = "**Person 2:** " + activity.Text });
Right now the format is plain background like below -
Person 1: Hello, How are you doing?
Person 2: Hi, I am doing great!
How can i format these plain messages to get conversation feel like we have in skype as
I am new to Windows Application development, I am not sure how to format the text as conversations within markdown text block, could you guide me?
Do I need to create a table within markdown control and pass the messages by having background color on rows? not sure how to do this. Any help?
Updated view -
<Page
x:Class="MyProject1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gif="using:XamlAnimatedGif"
xmlns:local="using:LISA_Speech1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
mc:Ignorable="d" Background="Black">
<Page.Resources>
<Style x:Key="MessageItemStyle" TargetType="SelectorItem">
<Setter Property="Height" Value="Auto" />
<Setter Property="Width" Value="450" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Padding" Value="10" />
<Setter Property="Margin" Value="5" />
</Style>
<Style
x:Key="RightAlignedMessageStyle"
BasedOn="{StaticResource MessageItemStyle}"
TargetType="SelectorItem">
<Setter Property="Background" Value="LightGray" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style
x:Key="LeftAlignedMessageStyle"
BasedOn="{StaticResource MessageItemStyle}"
TargetType="SelectorItem">
<Setter Property="Background" Value="Orange" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<styleSelectors:MessageContainerStyleSelector
x:Key="MessageContainerStyleSelector"
ReceivedStyle="{StaticResource LeftAlignedMessageStyle}"
Sender="{x:Bind CurrentUser, Mode=OneWay}"
SentStyle="{StaticResource RightAlignedMessageStyle}" />
<DataTemplate x:Key="MessageTemplate" x:DataType="messages:Message">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind Message, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
<StackPanel
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind SentDate, Mode=OneWay}" />
</StackPanel>
</Grid>
</DataTemplate>
<ItemsPanelTemplate x:Key="MessageItemPanelTemplate">
<ItemsStackPanel VerticalAlignment="Bottom" ItemsUpdatingScrollMode="KeepLastItemInView" />
</ItemsPanelTemplate>
</Page.Resources>
<Grid Background="Black" Name="mainGrid">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="50"></RowDefinition>
</Grid.RowDefinitions>
<Border BorderBrush="Cyan" BorderThickness="0.2" Margin="3,0,3,3">
<ListView
x:Name="Messages"
Margin="10"
CanDrag="False"
CanReorderItems="False"
IsItemClickEnabled="False"
IsTapEnabled="False"
ItemContainerStyleSelector="{StaticResource MessageContainerStyleSelector}"
ItemTemplate="{StaticResource MessageTemplate}"
ItemsPanel="{StaticResource MessageItemPanelTemplate}"
ItemsSource="{x:Bind Text, Mode=OneWay}" />
</Border>
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
<ColumnDefinition Width="35"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBox x:Name="text" KeyUp="TextBox_KeyDown" Grid.Column="0" PlaceholderText="Type something or say 'Start Listening'" FontSize="17" BorderBrush="Purple" Margin="0,10,0,-7" Height="58" VerticalAlignment="Top">
</TextBox>
<Button x:Name="button" Click="Button_Click" Grid.Column="1" Height="58" Width="35" Padding="0" Background="Purple" Margin="0,10,0,-7">
<SymbolIcon x:Name="symbol" Symbol="Microphone" Width="35" HorizontalAlignment="Center" Foreground="White" Margin="-2,-8,-2,-2"/>
</Button>
</Grid>
<MediaElement x:Name="Media"></MediaElement>
</Grid>
</Page>
You can achieve this quite simple using an ItemContainerStyleSelector. What this will do is allow you to create some logic that takes your chat message object and determine if it was sent or received.
For example, your model may look like this:
public class Message
{
public Guid Id { get; set; }
public string UserTo { get; set; }
public string UserFrom { get; set; }
public string Message { get; set; }
public DateTime SentDate { get; set; }
}
You then will create the StyleSelector like this:
public class MessageContainerStyleSelector : StyleSelector
{
public Style SentStyle { get; set; }
public Style ReceivedStyle { get; set; }
public string Sender { get; set; }
protected override Style SelectStyleCore(object item, DependencyObject container)
{
var message = item as Message;
if (message != null)
{
return message.UserFrom.Equals(this.Sender, StringComparison.CurrentCultureIgnoreCase)
? this.SentStyle
: this.ReceivedStyle;
}
return base.SelectStyleCore(item, container);
}
}
From here, we then need to create the styles that will be used with this style selector and they are very simple. They will also give you flexibility for the colors you're looking to use for the sent or received messages.
In your view, you may have styles set up like this:
<Page.Resources>
<Style x:Key="MessageItemStyle" TargetType="SelectorItem">
<Setter Property="Height" Value="Auto" />
<Setter Property="Width" Value="450" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="Padding" Value="10" />
<Setter Property="Margin" Value="5" />
</Style>
<Style
x:Key="RightAlignedMessageStyle"
BasedOn="{StaticResource MessageItemStyle}"
TargetType="SelectorItem">
<Setter Property="Background" Value="LightGray" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<Style
x:Key="LeftAlignedMessageStyle"
BasedOn="{StaticResource MessageItemStyle}"
TargetType="SelectorItem">
<Setter Property="Background" Value="Orange" />
<Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<styleSelectors:MessageContainerStyleSelector
x:Key="MessageContainerStyleSelector"
ReceivedStyle="{StaticResource LeftAlignedMessageStyle}"
Sender="{x:Bind CurrentUser, Mode=OneWay}"
SentStyle="{StaticResource RightAlignedMessageStyle}" />
<DataTemplate x:Key="MessageTemplate" x:DataType="messages:Message">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind Message, Mode=OneWay}"
TextWrapping="WrapWholeWords" />
<StackPanel
Grid.Row="1"
Margin="0,5,0,0"
HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{x:Bind SentDate, Mode=OneWay}" />
</StackPanel>
</Grid>
</DataTemplate>
<ItemsPanelTemplate x:Key="MessageItemPanelTemplate">
<ItemsStackPanel VerticalAlignment="Bottom" ItemsUpdatingScrollMode="KeepLastItemInView" />
</ItemsPanelTemplate>
</Page.Resources>
In these styles, you see that we are using a base style which the two chat message styles inherit from. We are using a right/left alignment in this scenario so your messages will show on either side of the screen but here you can customize each style to your own needs.
A few other things to point out here, we are also declaring the DataTemplate that will be used to show layout the message. Notice here we aren't doing anything custom. Every message will layout the same, it's the container style that will alter how they appear in the ListView.
Also the ItemsPanelTemplate at the bottom allows the ListView to present the messages bottom up in a chat style format.
In regards to how this all ties together with your ListView in the page, you would now use the MessageContainerStyleSelector like this:
<ListView
x:Name="Messages"
Margin="10"
CanDrag="False"
CanReorderItems="False"
IsItemClickEnabled="False"
IsTapEnabled="False"
ItemContainerStyleSelector="{StaticResource MessageContainerStyleSelector}"
ItemTemplate="{StaticResource MessageTemplate}"
ItemsPanel="{StaticResource MessageItemPanelTemplate}"
ItemsSource="{x:Bind Messages, Mode=OneWay}" />
When you're running the app, you'll get something that looks similar to this:
Hopefully this is something to go off and you can take this further with this detail. Feel free to ask any questions, I'm happy to help.
The first thing I would do is a custom control called MessageViewer. In this one you could have a parameter that tells you what's the direction of the message, or just two another classes MessageViewerIn and MessageViewerOut that could be like this:
XAML:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition Width="50"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Rectangle Grid.Column="1" Fill="LightBlue" RadiusX="15" RadiusY="15"></Rectangle>
<TextBlock Grid.Column="1" VerticalAlignment="Center" Margin="10" Text="Some dummy text here"></TextBlock>
</Grid>
Now, via code you could understand if you need an image on the LEFT or RIGHT column of this grid. Everything will depend if the text you're showing is from an user or if YOU are sending this text.
So, you could add some methods to check it (or directly do in in the constructor of this class like:
MessageViewer(String message, bool orientation)
MessageViewer msgVwr = new MessageViewer("Your Text", true/false)
...and if its true your icon will be on the left, and when it's false on the right. Inside of the control you can access various element, so for example, if you'll give a name for a grid
<Grid Name="grdMain">
you can acces his properties via code, like grdMain.Background, and set it and change it.
For the space on the left/right you could use margins of the interlat textbox, setted on "10, 10, 30, 10" for right-space (yours) messages and "30, 10, 10, 10" for left-space (incoming) messages.
For doing some text work, like bold or other, check out elements you can put inside a grid: everything you want :)
All this elements you could put in a simple ListView to show them like a chat.
So I'm trying to create a user control for an application I'm working on. It's basically a ToggleButton next to a ComboBox. I was able to pretty much mock the ComboBox portion of the user control up in VS2015 the way the designers want it, but I feel like the way I'm going about it is not exactly the best way.
First, here is a link to a screenshot of what it looks like:
https://www.dropbox.com/s/019f4xqgu8r4i0e/DropDown.png
To do this, I ended up creating 3 different ComboBoxItem styles. The first puts together a CheckBox, a TextBlock with the ContentPresenter, and a Rectangle. The second just has a Separator, and the last just has the TextBlock with the ContentPresenter. Here is my XAML, which is declared in the UserControl.Resources section:
<Style x:Key="cbTestStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="OverridesDefaultStyle" Value="True"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border Name="Border"
Padding="5"
Margin="2"
BorderThickness="2"
CornerRadius="0"
BorderBrush="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="75"/>
<ColumnDefinition Width="15"/>
</Grid.ColumnDefinitions>
<CheckBox Grid.Column="0"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBlock Grid.Column="1"
TextAlignment="Left"
Foreground="Black">
<ContentPresenter/>
</TextBlock>
<Rectangle Grid.Column="2"
Stroke="Black"
Width="15"
Height="15"
Fill="{TemplateBinding Foreground}"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Gray"/>
<Setter TargetName="Border" Property="Background" Value="LightGray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="cbSeparatorStyle" TargetType="ComboBoxItem">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Separator/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="cbResetStyle" TargetType="{x:Type ComboBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="VerticalAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border x:Name="Border"
Padding="5"
Margin="2"
BorderThickness="2"
CornerRadius="0"
BorderBrush="Transparent">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1">
<ContentPresenter/>
</TextBlock>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="True">
<Setter TargetName="Border" Property="BorderBrush" Value="Gray"/>
<Setter TargetName="Border" Property="Background" Value="LightGray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I guess my first question would be, is this the best way to make my ComboBox look like the screenshot I have presented?
Of course, there are deeper issues that I have yet to address. Firstly, the cbTestStyle of ComboBoxItem I want to be able to populate dynamically. Databinding would be my obvious go-to, but with the separator and "Reset" styles at the end, I'm not sure how to do this. I currently have the ComboBoxItems "hard-coded" in XAML:
<ComboBox x:Name="cbTestSelect"
Height="34"
Width="18"
IsEnabled="False">
<ComboBoxItem Style="{StaticResource cbTestStyle}" Content="Test 1" Foreground="#7FFF0000" Selected="ComboBoxItem_Selected"/>
<ComboBoxItem Style="{StaticResource cbTestStyle}" Content="Test 2" Foreground="#7F00FF00" Selected="ComboBoxItem_Selected"/>
<ComboBoxItem Style="{StaticResource cbTestStyle}" Content="Test 3" Foreground="#7F0000FF" Selected="ComboBoxItem_Selected"/>
<ComboBoxItem Style="{StaticResource cbSeparatorStyle}"/>
<ComboBoxItem Style="{StaticResource cbResetStyle}" Content="Reset all"/>
</ComboBox>
In this example, I would ideally like to dynamically create the first three items and have the separator and "reset" items remain static. I'm still relatively new to WPF. I felt like trying to create this control in WinForms (which the application this user control would be used in is) would be a lot more complicated. Plus I'm trying to steer us towards using WPF more anyway.
Any help or links to other questions or tutorials online would be greatly appreciated.
Solution 1:
Use a CompositeCollection so that you can bring up your data items with DataBinding, and use regular XAML to define the hard-coded items:
<Window x:Class="WpfApplication31.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication31"
Title="MainWindow" Height="350" Width="525"
x:Name="view">
<Window.Resources>
<DataTemplate DataType="{x:Type local:DataItem}">
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding IsChecked}"/>
<TextBlock Text="{Binding Text}"/>
<Rectangle Stroke="Black" StrokeThickness="1"
Fill="{Binding Color}" Width="20"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ComboBox VerticalAlignment="Center"
HorizontalAlignment="Center"
Width="100" x:Name="Combo">
<ComboBox.Resources>
<CompositeCollection x:Key="ItemsSource">
<CollectionContainer Collection="{Binding DataContext,Source={x:Reference view}}"/>
<Separator Height="10"/>
<Button Content="Clear All"/>
</CompositeCollection>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<StaticResource ResourceKey="ItemsSource"/>
</ComboBox.ItemsSource>
</ComboBox>
</Grid>
</Window>
Code Behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var colors = new[] {"Red", "Green", "Blue", "Brown", "Cyan", "Magenta"};
this.DataContext =
Enumerable.Range(0, 5)
.Select(x => new DataItem
{
Text = "Test" + x.ToString(),
Color = colors[x],
IsChecked = x%2 == 0
});
}
}
Data Item:
public class DataItem
{
public bool IsChecked { get; set; }
public string Text { get; set; }
public string Color { get; set; }
}
Result:
Solution 2:
Using Expression Blend, you can get the XAML for the default Template for the ComboBox control, and modify this XAML to accomodate your extra visuals.
The XAML you get is rather long, and I'm not going to post it here. You will have to put that in a ResourceDictionary and reference that in the XAML where you define this ComboBox.
The relevant part you need to edit is the Popup:
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2" IsOpen="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}" Margin="1" PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}" Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="shadow" Color="Transparent" MaxHeight="{TemplateBinding MaxDropDownHeight}" MinWidth="{Binding ActualWidth, ElementName=templateRoot}">
<Border x:Name="dropDownBorder" BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}" BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<DockPanel>
<Button Content="Clear All" DockPanel.Dock="Bottom"/>
<Separator Height="2" DockPanel.Dock="Bottom"/>
<ScrollViewer x:Name="DropDownScrollViewer">
<Grid x:Name="grid" RenderOptions.ClearTypeHint="Enabled">
<Canvas x:Name="canvas" HorizontalAlignment="Left" Height="0" VerticalAlignment="Top" Width="0">
<Rectangle x:Name="opaqueRect" Fill="{Binding Background, ElementName=dropDownBorder}" Height="{Binding ActualHeight, ElementName=dropDownBorder}" Width="{Binding ActualWidth, ElementName=dropDownBorder}"/>
</Canvas>
<ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Contained" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Grid>
</ScrollViewer>
</DockPanel>
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
Notice that I added a DockPanel, the Button and a Separator.
Then you can bind your ItemsSource to the DataItem collection normally:
<ComboBox ItemsSource="{Binding}"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Width="100"/>
Result:
Notice that this approach is a lot better than my previous solution, and other answers posted here, because it does not wrap the extra visuals in ComboBoxItems, and therefore you don't get the selection highlight for them, which is rather weird.
You could use a DataTemplateSelector with the DataTemplates defined in the XAML and some item type variable it the data you're binding to.
public class StyleSelector : DataTemplateSelector
{
public DataTemplate DefaultTemplate
{ get; set; }
public DataTemplate SeparatorTemplate
{ get; set; }
public DataTemplate ResetTemplate
{ get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var type = item as SomeType;
if (type != null)
{
switch (type.SomeItemTypeField)
{
case TypeENum.Separator: return SeparatorTemplate;
case TypeENum.Reset: return ResetTemplate;
default:
return DefaultTemplate;
}
}
return base.SelectTemplate(item, container);
}
}
Check out this more detailed example.
I think your best bet is to learn about DataTemplate and DataTemplateSelector.
Here is an blog post that will show you a simple example of using a DataTemplate.
The ComboBox Control
Essentially, you could bind your ComboBox to a collection of objects, and use a DataTemplateSelector to pick which template to use based on the type of object.
I am trying to hide the grid, controlled by the checkbox. Currently, when I use the box nothing happens. I cannot figure out why. Everything I have found online is exactly what I have.
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition />
</Grid.RowDefinitions>
<!--upper window..-->
<CheckBox x:Name="show" Grid.Row="1" IsChecked="False">Display Preview with Sliders?</CheckBox>
<Grid Grid.Row="1"
Visibility="{Binding ElementName=show, Path=isChecked, Converter={StaticResource BoolToVis}}">
<!--what I want to hide-->
</Grid>
</Grid>
It doesn't make any sense.
Property names are case sensitive. Replace isChecked with IsChecked in your binding.
Visibility="{Binding ElementName=show, Path=IsChecked,
Converter={StaticResource BoolToVis}}"
Try
Path=IsChecked
Even XAML is case-sensitive.
I advice to use a DataTrigger
<Style x:Key="CheckBoxStyle" TargetType="{x:Type CheckBoxStyle}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=show,Path=IsChecked,}" Value="False">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
How can I set Visibility="Visible" for the Button inside the Control Template when the IsSendBtnVisible property in the code-behind is true?
Here's my WPF page:
<Page
x:Class="CardViewPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CardViewPage">
<Grid Name="content" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DocumentViewer Margin="0" Grid.Row="0" Name="documentViewer" />
</Grid>
</Page>
Here's my Custom Template for the document viewer on this page:
<Style TargetType="{x:Type DocumentViewer}">
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DocumentViewer}">
...
<Button
Click="btnSendToServer_Click"
Width="25"
Height="25"
Visibility="Collapsed" />
...
</ControlTemplate>
</Setter>
...
</Style>
U need to declare DependancyProperty for ur DocumentViewer and use TemplateBinding in xaml ControlTemplate (UrProperty for example)
<ControlTemplate TargetType="{x:Type DocumentViewer}">
...
<Button Click="btnSendToServer_Click"
Width="25"
Height="25"
Visibility="{TemplateBinding UrProperty}"
/>
...
</ControlTemplate>
I suggest you to use data triggers to achieve this...
<Button
Click="btnSendToServer_Click"
Width="25"
Height="25">
<Button.Style>
<Style>
<Setter Property="Button.Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSendBtnVisible}" Value="True">
<Setter Property="Button.Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Make sure to set visibility to collapsed in the style but not in the button properties..
Note: Binding for data trigger may change depending on your data context
Dima Martovoi,bathineni - thanks for replies.
Dima Martovoi, i think inherit from DocumentViewer is to hard for this small problem.
I tried to use variant with DataTrigger from bathineni's solution, but it's not works. Don't know, why.
Problem was solved using next binding:
<Button
Visibility="{Binding RelativeSource={RelativeSource AncestorType=Page},Path=SendToServerVisiblity}">
</Button>
where
public Visibility SendToServerVisiblity
{
get
{
if (IsOnlineMode)
return Visibility.Visible;
return Visibility.Collapsed;
}
}
in page code-behind