I'm working on making a simple tic-tac-toe game to familiarize myself with WPF, due to the amount of time it's been since I've used the format. I've been trying to disable the button, to prevent people from choosing the same square, after the content has been set to X or O. Here is a sample of my XAML code for the buttons:
<Button Name="btmRight" Margin="10 10 10 10" Grid.Column="2" Grid.Row="2" FontSize="128">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="1"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Background" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
The buttons are enabled by default. I then use some simple C# code to disable the button, and set the content to X or O depending on the turn. However, despite the style trigger being in each button, the format is never changed. Something I might explore is putting a conditional in the code, to not change anything with the buttons if they have content, rather than disabling the button when it's clicked. Here is a sample of what I'm using for the C#:
mid.Click += (sender, e) =>
{
mid.Content = mark;
TurnSwap();
mid.IsEnabled = false;
IsComplete();
};
"mark" is a String that is set depending on the turn. The content is set to the mark value, which then rotates immediately after the content is set. The button is then disabled, and the game checks to see if someone has won yet. If I were to go with the option of manually disabling the button, I would surround that C# code in a conditional, to check if the content = "", which it is by default. I feel this option is a bit lazy, and I would like to learn the WPF solution for the concepts alone.
The following code is just a sample based on your question. I does not have tic-tac-toe game logic implemented.
It demonstraits how to implement common style to button and also implmentation of common ClickEvent.
You can use it and change it as per you need.
XAML
<Window x:Class="WPFTest.MainWindow"
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:WPFTest"
mc:Ignorable="d"
Title="TestWPF" Height="300" Width="400"
WindowStyle="None" WindowStartupLocation="CenterScreen">
<Window.Resources>
<Style x:Key="buttonStyle" TargetType="Button">
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Background" Value="MediumAquamarine" />
<Setter Property="Foreground" Value="MediumBlue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Background="{TemplateBinding Background}">
<ContentPresenter x:Name="MyContentPresenter"
Content="{TemplateBinding Content}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Opacity" Value="1"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="Background" Value="LightGray"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="btn1" Grid.Row="0" Grid.Column="0" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
<Button x:Name="btn2" Grid.Row="0" Grid.Column="1" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
<Button x:Name="btn3" Grid.Row="0" Grid.Column="2" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
<Button x:Name="btn4" Grid.Row="1" Grid.Column="0" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
<Button x:Name="btn5" Grid.Row="1" Grid.Column="1" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
<Button x:Name="btn6" Grid.Row="1" Grid.Column="2" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
<Button x:Name="btn7" Grid.Row="2" Grid.Column="0" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
<Button x:Name="btn8" Grid.Row="2" Grid.Column="1" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
<Button x:Name="btn9" Grid.Row="2" Grid.Column="2" Style="{StaticResource buttonStyle}" Click="btn_Click"/>
</Grid>
</Window>
CS
using System;
using System.Windows;
using System.Windows.Controls;
namespace WPFTest
{
public partial class MainWindow : Window
{
string sign = "X";
public MainWindow()
{
InitializeComponent();
}
private void btn_Click(object sender, RoutedEventArgs e)
{
try
{
Button currentButton = sender as Button;
currentButton.Content = sign;
currentButton.IsEnabled = false;
swapSign();
}
catch (Exception ex)
{
}
}
private void swapSign()
{
if (sign == "X")
sign = "O";
else
sign = "X";
}
}
}
UPDATE-1
I changed the style in XAML. Check above. One thing to note here is that you have to set Background and Foreground properties of Button Style like I set.
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 adding a WPF DatePicker control to my form and it works fine. But I don't want the user to be able to type in a type in the 'Select a date' textbox. I want that to be readonly and when they click the textbox it just opens the calendar.
I wasn't sure if there was an option for this in the properties? I couldn't find anything...
<DatePicker HorizontalAlignment="Left" VerticalAlignment="Top">
<DatePicker.Resources>
<Style TargetType="DatePickerTextBox">
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="Text" Value=" "/>
<Setter Property="Focusable" Value="False"/>
<Setter Property="IsEnabled" Value="False" />
<Setter Property="IsHitTestVisible" Value="False"/>
</Style>
</DatePicker.Resources>
</DatePicker>
In future you can use the WPF visualizer to see which child controls a top-level control is using (in this case DatePickerTextBox) and then apply a style and/or template to that type in the resources section like I've done here.
The answer of Mark Feldman is partially right, he had not answer to how clicking on the textbox gonna open the calendare popup!
You should hook the template of the DatePicker and enlarge the size of the button so it will overlap the textbox: then you are done, when you click on the textbox it's the button that get the event see below a part of the modified template of DatePicker:
<DatePickerTextBox Grid.Row="0" Grid.Column="0" x:Name="PART_TextBox" Focusable="{TemplateBinding Focusable}" HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch"/>
<Button Grid.Row="0" Grid.Column="0" x:Name="PART_Button" Foreground="{TemplateBinding Foreground}" Focusable="False" >
<Button.Template>
<ControlTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Border Background="Transparent"/>
<Button Grid.Column="1" HorizontalAlignment="Left" Foreground="{TemplateBinding Foreground}" Template="{StaticResource DropDownButtonTemplate}" VerticalAlignment="Top" Width="20" Margin="3,0,3,0" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
Dont forget to add the style of DatePickerTextBox described in the answer of Mark Feldman
You can open calendar this way :
<DatePicker Name="datePicker" Focusable="False">
<DatePicker.Resources>
<Style TargetType="DatePickerTextBox">
<EventSetter Event="MouseLeftButtonUp" Handler="OnMouseLeftButtonUp" />
</Style>
</DatePicker.Resources>
</DatePicker>
private void OnMouseLeftButtonUp(object sender, RoutedEventArgs e)
{
datePicker.IsDropDownOpen = true;
}
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