I have a very Unusual situation. I need to show a User Control on a Form to get the users input. But I would like to not have to try and retrieve the information through events. The reason is that it splits up my code to do one thing into three different methods and I would like to try and prevent that. If there is a way I can do this, that would be awesome.
I have tried using byRef variables but because they are both in the UI thread my UserControl does not get shown.
Thank you in advance
Window
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:me="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<StackPanel >
<Button Name="btnTest" Click="btnTest_Click" Content="Click Me!" />
<Button Name="btnTest2" Click="btnTest2_Click" Content="Click Me Too!" />
</StackPanel>
<me:ucInputBox x:Name="Input" Visibility="Collapsed" />
</Grid>
User Control
<UserControl x:Class="ucInputBox"
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"
mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" x:Name="InputBox">
<UserControl.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="Border" Background="{TemplateBinding Background}" CornerRadius="4" BorderThickness="2">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Background" Value="#ECECEC" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="Border" Property="Background" Value="#CECECE" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Opacity" Value="0.7" />
<Setter Property="Foreground" Value="Gray"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid >
<Grid Background="Black" Opacity="0.5"/>
<Border
MinWidth="250"
Background="WhiteSmoke"
BorderBrush="Black"
BorderThickness="1"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<StackPanel >
<TextBlock Name="txbInput" Margin="10" Text="Input Box:" FontWeight="Bold" FontFamily="Cambria" />
<TextBox MinWidth="150" HorizontalAlignment="Center" VerticalAlignment="Center" x:Name="InputTextBox" Width="210" TextWrapping="WrapWithOverflow" />
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,5,0,0">
<Button x:Name="OKButton" Content="Ok" Margin="5" Click="OKButton_Click" IsDefault="True" Height="30" Width="50"/>
<Button x:Name="CancelButton" Content="Cancel" Margin="5" Click="CancelButton_Click" IsCancel="True" Height="30" Width="50" />
</StackPanel>
</StackPanel>
</Border>
</Grid>
Code Behind
Class MainWindow
#Region " Split Method"
Private Sub btnTest_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
'\\\\\\\\\ Button using split method
Input.Visibility = Windows.Visibility.Visible
End Sub
Private Sub Input_Submit(Response As String) Handles Input.Submit
'\\\\\\\ since this is the event all code that depends on the input goes here.
'\\\\\\\ If Multiple methods need input this blows up
End Sub
#End Region
Private Sub btnTest2_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
Input.HandledLocally = True
'\\\\\\\\\ This method gets stuck and never shows becuase the while loop is in the UI thread
Dim holder = Input.GetInput()
End Sub
End Class
Code Behind User Control
Public Class ucInputBox
Public Event Submit(ByVal Response As String)
Private _TextBoxString As String
Public Property TextBoxString() As String
Get
Return _TextBoxString
End Get
Set(ByVal value As String)
_TextBoxString = value
txbInput.Text = _TextBoxString
End Set
End Property
Private _WaitingForInput As Boolean = True
Public Property WaitingForInput() As Boolean
Get
Return _WaitingForInput
End Get
Set(ByVal value As Boolean)
_WaitingForInput = value
End Set
End Property
Private _HandledLocally As Boolean
Public Property HandledLocally() As Boolean
Get
Return _HandledLocally
End Get
Set(ByVal value As Boolean)
_HandledLocally = value
End Set
End Property
Public Function GetInput() As String
InputBox.Visibility = Windows.Visibility.Visible
While Not WaitingForInput
'\\\\\\ Wait for input
End While
Dim ret As String
ret = InputTextBox.Text
InputTextBox.Text = String.Empty
Return ret
End Function
Private Sub CancelButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
InputBox.Visibility = Windows.Visibility.Collapsed
InputTextBox.Text = String.Empty
If HandledLocally Then
WaitingForInput = False
Else
RaiseEvent Submit(String.Empty)
End If
End Sub
Private Sub OKButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs)
InputBox.Visibility = Windows.Visibility.Collapsed
If HandledLocally Then
WaitingForInput = False
Else
RaiseEvent Submit(String.Empty)
InputTextBox.Text = String.Empty
End If
'RaiseEvent Submit(String.Empty)
'InputTextBox.Text = String.Empty
End Sub
Private Sub UserControl_Loaded(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles MyBase.Loaded
txbInput.Text = _TextBoxString
InputTextBox.Focus()
OKButton.IsEnabled = False
End Sub
Private Sub InputTextBox_TextChanged(sender As System.Object, e As System.Windows.Controls.TextChangedEventArgs) Handles InputTextBox.TextChanged
If InputTextBox.Text.Trim <> String.Empty Then
OKButton.IsEnabled = True
Else
OKButton.IsEnabled = False
End If
End Sub
End Class
Related
I create a template for buttons:
<Style x:Key="TableButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Background" Value="{StaticResource GrayBlueGradientBrush}" />
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="100" />
<Setter Property="Margin" Value="20" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}" ClipToBounds="True">
<!-- Inner Rectangle with rounded corners. -->
<Rectangle x:Name="innerRectangle"
Stroke="Transparent"
StrokeThickness="20"
Fill="{TemplateBinding Background}"
RadiusX="20" RadiusY="20" />
<Ellipse x:Name="NumberGuests"
Width="25"
Height="25"
Stretch="Fill"
StrokeLineJoin="Round"
Fill="Red"
Margin="-70,-70,0,0"/>
<Ellipse x:Name="NumberChecks"
Width="25"
Height="25"
Stretch="Fill"
StrokeLineJoin="Round"
Fill="Green"
Margin="70,-70,0,0"/>
<Rectangle x:Name="Time"
Width="70"
Height="25"
Fill="White"
RadiusX="10" RadiusY="10"
Margin="0,50,0,0"/>
<TextBlock x:Name='TableID'
FontSize="12"
HorizontalAlignment="Center"
FontWeight="Bold"
Margin="0,25,0,0"
Text="Table_New"/>
<TextBlock x:Name='TimeID'
FontSize="12"
HorizontalAlignment="Center"
Margin="0,65,0,0"
Text="Time_New" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Opacity" Value="0.5" />
</Trigger>
</Style.Triggers>
</Style>
Private Sub CreateButton_Click(sender As Object, e As RoutedEventArgs)
Dim TableName As String
Dim TimeNotAvailable As String
LayoutGrid.AllowDragging = True
ToggleButton.Content = "Edit"
Dim LocationButton As New Button
Dim style As Style = TryCast(Me.FindResource("TableButtonStyle"), Style)
TableName = "Test" & I
TimeNotAvailable = "Time" & I
I += 1
LocationButton.Style = style
TableID.Content = TableName
TimeID.Content = TimeNotAvailable
LayoutGrid.Children.Add(LocationButton)
AddHandler LocationButton.Click, AddressOf LocationButtonClicked
End Sub
Every time a "Create Button" button is pressed, a button will be generated based on the template.
However I am not able to set the textblock.text TableID and TimeID.
When creating the buttons I need the tableID and TimeID to get the value of a variable. Like "Table 1", "Table 2" etc.
I tried all different type of bindings, resources etc but binding would change the text in all buttons, first button text would be "Table 1" but when generating the second both buttons would be "Table 2". I also tried DataContext but the both textblocks would have the same data.
I tried creating a button directly in xaml and "TableID.Content = TableName" worked but when using the template "TableID" is not recognized.
Please some assistance. Thanks
As a quick solution you can try this workaround
Add binding to the Text property as follows
<TextBlock x:Name='TableID'
FontSize="12"
HorizontalAlignment="Center"
FontWeight="Bold"
Margin="0,25,0,0"
Text="{Binding [0], FallbackValue=Table_New}"/>
<TextBlock x:Name='TimeID'
FontSize="12"
HorizontalAlignment="Center"
Margin="0,65,0,0"
Text="{Binding [1], FallbackValue=Time_New}" />
and replace following
TableID.Content = TableName
TimeID.Content = TimeNotAvailable
with
LocationButton.DataContext = { TableName, TimeNotAvailable }
The above example demonstrates MVVM way of displaying data on the UI.
Explanation
{ TableName, TimeNotAvailable } creates and an implicit array which passed as DataContext to the button then it is used in binding with indexers where [0] is first element and [1] being the second.
I have added context menu to vertex By following code
<Style TargetType="{x:Type graphsharp:VertexControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type graphsharp:VertexControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="10,10,10,10"
Padding="{TemplateBinding Padding}">
<Border.ContextMenu>
<ContextMenu Opened="ContextMenu_Opened">
<MenuItem x:Name="miDelete" Click="miAdd_Click" Header="Delete" />
</ContextMenu>
</Border.ContextMenu>
<ContentPresenter Content="{TemplateBinding Vertex}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Added events to code page by
Public Sub miAdd_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
End Sub
Public Sub ContextMenu_Opened(ByVal sender As System.Object, ByVal e As System.EventArgs)
End Sub
But how can i get the id or value of vertex to delete it
sample in C# - I think rewrite to vb will have no problem...
<Style TargetType="{x:Type GraphSharp_Controls:VertexControl}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<EventSetter Event="Click" Handler="v_ContextMenuClick"/>
</Style>
</ContextMenu.ItemContainerStyle>
<MenuItem Header="Delete this vertex" CommandParameter="{x:Static SampleProject:ContextCommands.REMOVE}">
<MenuItem.Icon>
<Image Source="/del.png" />
</MenuItem.Icon>
</MenuItem>
...
</ContextMenu>
</Setter.Value>
</Setter>
<!-- where 'ContextCommands' is just my pre-defined enums... -->
...
and this is click handler
private void v_ContextMenuClick(object sender, RoutedEventArgs e){
MenuItem mi = (MenuItem)sender;
VertexControl vc = null;
if (mi != null){
vc = (VertexControl)((ContextMenu)mi.Parent).PlacementTarget;
//my own class inherited from GraphSharp.Controls.VertexControl
//you can use what you have directly
PocVertex pv = (PocVertex)vc.Vertex;
//same thing with graph class
//graph is stored globally
PocGraph gg = App.vm.Graph;
switch ((ContextCommands)mi.CommandParameter){
case ContextCommands.REMOVE:
gg.RemoveVertex(pv);
break;
...
}
}
e.Handled = true;
}
fyi - handler function must be stored under xaml.cs code to be visible.
hope this helps...
This is my xaml code:
<UserControl x:Class="UserControl1"
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"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<Style TargetType="TextBox" xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Style.Resources>
<VisualBrush x:Key="CueBannerBrush" AlignmentX="Left" AlignmentY="Center" Stretch="None">
<VisualBrush.Visual>
<Label Content="{Binding Path=CueBannerText}" Foreground="LightGray" />
</VisualBrush.Visual>
</VisualBrush>
</Style.Resources>
<Style.Triggers>
<Trigger Property="Text" Value="{x:Static sys:String.Empty}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="Text" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource CueBannerBrush}" />
</Trigger>
<Trigger Property="IsKeyboardFocused" Value="True">
<Setter Property="Background" Value="White" />
</Trigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Grid>
<TextBox TextWrapping="Wrap"/>
</Grid>
</UserControl>
I want to bind set the Label Content by code if I call the control in my project. The way I do it is like this:
Public Class UserControl1
Public Property CueBannerText As String
Get
Return _oText
End Get
Set(value As String)
_oText = value
End Set
End Property
Private _oText As String = "Search"
Public Sub New()
InitializeComponent()
Me.DataContext = Me
End Sub
End Class
If I call my control in the code With this:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:Noru="clr-namespace:NoruTextBox;assembly=NoruTextBox">
<Grid>
<Noru:UserControl1></Noru:UserControl1>
</Grid>
</Window>
my control won't show 'Search' as the TextBox isn't selected or contains anything.
To do what you want, you'll either need to implement the INotifyPropertyChanged Interface in your UserControl code behind, or declare a DependencyProperty instead:
Public Shared CueBannerTextProperty As DependencyProperty = DependencyProperty.
Register("CueBannerText", GetType(String), GetType(TestView),
New PropertyMetadata("Search"))
Public Property CueBannerText() As String
Get
Return DirectCast(GetValue(CueBannerTextProperty), String)
End Get
Set
SetValue(CueBannerTextProperty, value)
End Set
End Property
Disclaimer: I just converted this to VB using an online converter, so I can't confirm its correctness.
Using a DependencyProperty will also enable you to set this value in a Style, Animation (unlikely in this situation), or a Binding:
<Noru:UserControl1 CueBannerText="{Binding SomeValue}" />
I am new to WPF and custom controls.
I have implemented a custom control in VB with WPF and MVVM based on Davids example: http://davidowens.wordpress.com/2009/02/18/wpf-search-text-box/
I have a custom control libary and a reference to my project to implement my control.
I implement my contol in the View via XAML:
EDIT:
<Window
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:UIControls;assembly=UIControls"
mc:Ignorable="d"
x:Class="LoginView"
x:Name="LoginView"
Width="457" Height="216" Visibility="Visible" xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<l:ExpandedTextBox Name="UsernameText" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,0,5,5" />
</Grid>
When I instantiate my custom control once everything works fine,
<l:ExpandedTextBox Name="UsernameText" />
but as soon as I try to instantiate it twice in the same View I get an error code:
Error 1 Cannot create an instance of "ExpandedTextBox"
EDIT:
The XAML code with the error looks like this:
<Window
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:UIControls;assembly=UIControls"
mc:Ignorable="d"
x:Class="LoginView"
x:Name="LoginView"
Width="457" Height="216" Visibility="Visible" xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors">
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.7*"/>
<ColumnDefinition Width="3*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<l:ExpandedTextBox Name="UsernameText" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,0,5,5" />
<l:ExpandedTextBox Name="Password" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Margin="10,0,5,5" />
</Grid>
I named the second custom control different and my first custom control still works fine.
The error only appears with the second custom control "Password".
The corresponding Generic.xaml file looks like this:
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:UIControls">
<SolidColorBrush x:Key="ExpandedTextBox_Background" Color="White" />
<SolidColorBrush x:Key="ExpandedTextBox_Foreground" Color="Black" />
<LinearGradientBrush x:Key="ExpandedTextBox_Border" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFABADB3" Offset="0.05" />
<GradientStop Color="#FFE2E3EA" Offset="0.07" />
<GradientStop Color="#FFE3E9EF" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ExpandedTextBox_BorderMouseOver" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FF5C97C1" Offset="0.05" />
<GradientStop Color="#FFB9D7EB" Offset="0.07" />
<GradientStop Color="#FFC7E2F1" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="ExpandedTextBox_SearchIconBorder" Color="White" />
<SolidColorBrush x:Key="ExpandedTextBox_SearchIconBackground" Color="White" />
<LinearGradientBrush x:Key="ExpandedTextBox_SearchIconBorder_MouseOver" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Color="#FFFFFFFF" Offset="0" />
<GradientStop Color="#FFE5F4FC" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ExpandedTextBox_SearchIconBackground_MouseOver" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Color="#FFE7F5FD" Offset="0" />
<GradientStop Color="#FFD2EDFC" Offset="0.5" />
<GradientStop Color="#FFB6E3FD" Offset="0.51" />
<GradientStop Color="#FF9DD5F3" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ExpandedTextBox_SearchIconBorder_MouseDown" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Color="#FFFFFFFF" Offset="0" />
<GradientStop Color="#FFE5F4FC" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="ExpandedTextBox_SearchIconBackground_MouseDown" StartPoint="0,0" EndPoint="0,1" >
<GradientStop Color="#FFE7F5FD" Offset="0" />
<GradientStop Color="#FFD2EDFC" Offset="0.5" />
<GradientStop Color="#FFB6E3FD" Offset="0.51" />
<GradientStop Color="#FF9DD5F3" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="ExpandedTextBox_LabelTextColor" Color="Gray" />
<Style x:Key="{x:Type l:ExpandedTextBox}" TargetType="{x:Type l:ExpandedTextBox}">
<Setter Property="Background" Value="{StaticResource ExpandedTextBox_Background}" />
<Setter Property="BorderBrush" Value="{StaticResource ExpandedTextBox_Border}" />
<Setter Property="Foreground" Value="{StaticResource ExpandedTextBox_Foreground}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="LabelText" Value="Username" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Text" Value="" />
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="LabelTextColor" Value="{StaticResource ExpandedTextBox_LabelTextColor}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type l:ExpandedTextBox}">
<Border x:Name="Border"
Padding="2"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="LayoutGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=ActualHeight}" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="PART_ContentHost" Grid.Column="0" />
<Label x:Name="LabelText"
Grid.Column="0"
Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelTextColor}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelText}"
Padding="2,0,0,0"
FontStyle="Italic" />
<Border x:Name="PART_SearchIconBorder"
Grid.Column="2"
Visibility="Collapsed"
BorderThickness="1"
Padding="1"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
BorderBrush="{StaticResource ExpandedTextBox_SearchIconBorder}"
Background="{StaticResource ExpandedTextBox_SearchIconBackground}">
<Image x:Name="SearchIcon"
Stretch="None"
Width="Auto"
Height="Auto"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="pack://application:,,,/UIControls;component/Images/clear.png" />
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource ExpandedTextBox_BorderMouseOver}" />
</Trigger>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="BorderBrush" Value="{StaticResource ExpandedTextBox_BorderMouseOver}" />
</Trigger>
<Trigger Property="HasText" Value="True">
<Setter Property="Visibility" TargetName="LabelText" Value="Hidden" />
<Setter Property="Visibility" TargetName="PART_SearchIconBorder" Value="Visible" />
</Trigger>
<Trigger Property="IsMouseOver" SourceName="PART_SearchIconBorder" Value="True">
<Setter Property="HasClicked" Value="True" />
<Setter Property="Background" TargetName="PART_SearchIconBorder" Value="#FFF0F5" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
The ExpandedTextBox VB file looks like this:
Imports System.Windows.Controls.Primitives
Imports System.ComponentModel
Public Class ExpandedTextBox
Inherits System.Windows.Controls.TextBox
Public Property LabelTextProperty As DependencyProperty = DependencyProperty.Register("LabelText", GetType(String), GetType(ExpandedTextBox))
Public Property LabelTextColorProperty As DependencyProperty = DependencyProperty.Register("LabelTextColor", GetType(Brush), GetType(ExpandedTextBox))
Public Property HasTextPropertyKey As DependencyProperty = DependencyProperty.Register("HasText", GetType(Boolean), GetType(ExpandedTextBox), New PropertyMetadata())
Public Property HasTextProperty As DependencyProperty = HasTextPropertyKey
Public Property HasClickedPropertyKey As DependencyProperty = DependencyProperty.Register("HasClicked", GetType(Boolean), GetType(ExpandedTextBox), New PropertyMetadata())
Public Property HasClickedProperty As DependencyProperty = HasClickedPropertyKey
Public Property HasClicked As Boolean
Get
Return CType(GetValue(HasClickedProperty), Boolean)
End Get
Set(ByVal value As Boolean)
SetValue(HasClickedProperty, value)
End Set
End Property
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Input.MouseButtonEventArgs)
If HasClicked = True Then
Me.Text = ""
Else
MyBase.OnMouseDown(e)
End If
End Sub
Public Property LabelText As String
Get
Return CType(GetValue(LabelTextProperty), String)
End Get
Set(ByVal value As String)
SetValue(LabelTextProperty, value)
End Set
End Property
Public Property LabelTextColor As Brush
Get
Return CType(GetValue(LabelTextColorProperty), Brush)
End Get
Set(ByVal value As Brush)
SetValue(LabelTextColorProperty, value)
End Set
End Property
Public Property HasText As Boolean
Get
Return CType(GetValue(HasTextProperty), Boolean)
End Get
Set(ByVal value As Boolean)
SetValue(HasTextProperty, value)
End Set
End Property
Shared Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(ExpandedTextBox), New FrameworkPropertyMetadata(GetType(ExpandedTextBox)))
End Sub
Protected Overrides Sub OnTextChanged(ByVal e As TextChangedEventArgs)
MyBase.OnTextChanged(e)
HasText = Text.Length <> 0
End Sub
End Class
Why can't I create the same custom control twice in my view ?
Thanks in advance !
This may be because you specified x:Name attribute of ExpendedTextBox UserControl.
Grid you are adding controls to is aware of their names, that's why it throws exception if it detects multiple controls in its children collection with the same x:Name.
We solved the problem with the comment of Rich. Thanks for that !
The problem were the dependcy properties, which weren't set at shared in the VB file. The new working file looks like this.
Imports System.Windows.Controls.Primitives
Imports System.ComponentModel
Public Class ExpandedTextBox
Inherits System.Windows.Controls.TextBox
Public Shared Property LabelTextProperty As DependencyProperty
Public Shared Property LabelTextColorProperty As DependencyProperty
Public Shared Property HasTextPropertyKey As DependencyProperty
Public Shared Property HasTextProperty As DependencyProperty
Public Shared Property HasClickedPropertyKey As DependencyProperty
Public Shared Property HasClickedProperty As DependencyProperty
Shared Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(ExpandedTextBox), New FrameworkPropertyMetadata(GetType(ExpandedTextBox)))
LabelTextProperty = DependencyProperty.Register("LabelText", GetType(String), GetType(ExpandedTextBox))
LabelTextColorProperty = DependencyProperty.Register("LabelTextColor", GetType(Brush), GetType(ExpandedTextBox))
HasTextPropertyKey = DependencyProperty.Register("HasText", GetType(Boolean), GetType(ExpandedTextBox), New PropertyMetadata())
HasTextProperty = HasTextPropertyKey
HasClickedPropertyKey = DependencyProperty.Register("HasClicked", GetType(Boolean), GetType(ExpandedTextBox), New PropertyMetadata())
HasClickedProperty = HasClickedPropertyKey
End Sub
Public Property HasClicked As Boolean
Get
Return CType(GetValue(HasClickedProperty), Boolean)
End Get
Set(ByVal value As Boolean)
SetValue(HasClickedProperty, value)
End Set
End Property
Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Input.MouseButtonEventArgs)
If HasClicked = True Then
Me.Text = ""
Else
MyBase.OnMouseDown(e)
End If
End Sub
Public Property LabelText As String
Get
Return CType(GetValue(LabelTextProperty), String)
End Get
Set(ByVal value As String)
SetValue(LabelTextProperty, value)
End Set
End Property
Public Property LabelTextColor As Brush
Get
Return CType(GetValue(LabelTextColorProperty), Brush)
End Get
Set(ByVal value As Brush)
SetValue(LabelTextColorProperty, value)
End Set
End Property
Public Property HasText As Boolean
Get
Return CType(GetValue(HasTextProperty), Boolean)
End Get
Set(ByVal value As Boolean)
SetValue(HasTextProperty, value)
End Set
End Property
Protected Overrides Sub OnTextChanged(ByVal e As TextChangedEventArgs)
MyBase.OnTextChanged(e)
HasText = Text.Length <> 0
End Sub
End Class
Thanks for all the contribution !
Awesome StackOverflow !
I have a ItemsControl with a collection of objects. I wan't to be able to click the object and then get a panels with more info.
So I decided to style the DataTemplate for the items in the ItemsControl as a button and that seems to work fine. However I have no idea how to set the click event of this button in the style. It says I should use a EventSetter but I can't get that to work.
Here is the code:
<Style TargetType="Expander" >
<Style.Resources>
<Style TargetType="ItemsControl" >
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="ItemsControl">
<Border BorderThickness="0,1,0,1" BorderBrush="{StaticResource DarkColorBrush}" >
<ScrollViewer Margin="0" VerticalScrollBarVisibility="Auto"
Focusable="false">
<StackPanel Margin="2" IsItemsHost="True" />
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate" >
<Setter.Value>
<DataTemplate DataType="{x:Type data:CompanyViewModel}" >
<Button>
<Button.Resources>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Name="Bd" BorderBrush="{StaticResource DarkColorBrush}"
BorderThickness="1"
Margin="5"
CornerRadius="8">
<Border.Background>
<!-- Removed for brevity -->
</Border.Background>
<StackPanel Orientation="Vertical">
<TextBlock Margin="5" Text="{Binding Path=Name}" Style="{StaticResource MenuText}" FontSize="16" HorizontalAlignment="Center" />
<TextBlock Margin="5,0,5,5" Text="{Binding Path=Code, StringFormat=Kt. {0}}" Style="{StaticResource MenuText}" HorizontalAlignment="Center" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="true">
<Setter TargetName="Bd" Property="Background">
<Setter.Value>
<!-- Removed for brevity -->
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="Button.IsPressed" Value="true">
<Setter TargetName="Bd" Property="Background">
<Setter.Value>
<!-- Removed for brevity -->
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Resources>
</Button>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="Expander">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="30" />
</Grid.ColumnDefinitions>
<ToggleButton Grid.Column="1"
IsChecked="{Binding Path=IsExpanded,Mode=TwoWay,
RelativeSource={RelativeSource TemplatedParent}}" />
<ContentPresenter Name="Content" Grid.Column="0" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter TargetName="Content" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Decided to add what I wanted to accomplish with the button click:
<Button Click="CompanyClick" />
CompanyClick being defined in the code behind.
Change
<Button>
To...
<Button Command="{Binding OnClick}" />
On the class you use as an item in this ItemsControl, implement a read-only property which returns an ICommand for the button to use.
EDIT:
For this example, I made use of an implementation of ICommand called RelayCommand, which is available at http://msdn.microsoft.com/en-us/magazine/dd419663.aspx. See figure 3 of that article for the full RelayCommand class in C#. I converted it to Visual Basic for my use, that code is below. It does nothing more than automate the registration of commands with the WPF system, and provides you with a convenient constructor:
''' <summary>
''' Implements the ICommand interface
''' </summary>
''' <remarks>
''' Thanks to Josh Smith for this code: http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
''' </remarks>
Public Class RelayCommand
Implements ICommand
#Region "Fields"
Private ReadOnly _execute As Action(Of Object)
Private ReadOnly _canExecute As Predicate(Of Object)
#End Region ' Fields
#Region "Constructors"
Public Sub New(ByVal execute As Action(Of Object))
Me.New(execute, Nothing)
End Sub
Public Sub New(ByVal execute As Action(Of Object), ByVal canExecute As Predicate(Of Object))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
_execute = execute
_canExecute = canExecute
End Sub
#End Region ' Constructors
#Region "ICommand Members"
<DebuggerStepThrough()>
Public Function CanExecute(ByVal parameter As Object) As Boolean Implements ICommand.CanExecute
Return If(_canExecute Is Nothing, True, _canExecute(parameter))
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
AddHandler CommandManager.RequerySuggested, value
End AddHandler
RemoveHandler(ByVal value As EventHandler)
RemoveHandler CommandManager.RequerySuggested, value
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Sub Execute(ByVal parameter As Object) Implements ICommand.Execute
_execute(parameter)
End Sub
#End Region ' ICommand Members
End Class
Using that class, you can then implement ICommands on your ViewModel, by exposing an ICommand as a read-only property in that class, along with a backing field to store the RelayCommand, which, don't forget, implements ICommand. Here's a truncated sample:
Public Class CompanyViewModel
Implements INotifyPropertyChanged
Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private _OnClick As RelayCommand
Public ReadOnly Property OnClick As ICommand
Get
If _OnClick Is Nothing Then
_OnClick = New RelayCommand(Sub()
Me.OnClickExecute()
End Sub,
Function()
Return Me.OnClickCanExecute()
End Function)
End If
Return _OnClick
End Get
End Property
Private Function OnClickCanExecute() As Boolean
' put a test here to tell the system whether conditions are right to execute your command.
' OR, just return True and it will always execute the command.
End Function
Private Sub OnClickExecute()
' put the processing for your command here; THIS IS YOUR EVENT HANDLER
End Sub
' .... implement the rest of your ViewModel
End Class
The "OnClick" name is not required; the commands can take any name, because the system is not convention-based the way that VB6 was with its event handlers.
There is more than one way to do this. I'm intrigued by the "Caliburn.Micro" implementation of ICommand, which is convention-based and might make things more readable, depending on your style. But, Caliburn is an open-sourced effort by an enthusiast, albeit a very competent and qualified enthusiast. Google or Bing "Caliburn.Micro" for more information on that.
There's also this:
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<!-- Animations manipulating the button here -->
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!-- The rest of your triggers here -->
</ControlTemplate.Triggers>
This kind of mechanism in your template will give you control over the properties of the button, and possibly properties in other parts of the visual tree, depending on where you put the definiton.
You might also consider architecting things a bit differently. I wouldn't necessarily pile all the definitions into the style in quite the same way.