I have a winforms application that I'd like to add a WPF user control to. The issue is when I run the program the WPF user control does not respond to mouse events. I can tab through the control and use the keyboard just fine, but the control does not respond to mouse clicks (the buttons don't even recognize when you hover over them). I have attempted the solution in this SO question but it did not help: WPF WinForms Interop issue with Enable / Disable . Please see the code below.
User Control XAML
<UserControl x:Class="WinformsWPF.ucNotes"
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="400" d:DesignWidth="460" Background="#FFD7DFEC" IsHitTestVisible="False" Loaded="Window_Loaded">
<UserControl.Resources>
<LinearGradientBrush x:Key="DataGridHeaderBackgroundBrush" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#b3dbcc" Offset="0" />
<GradientStop Color="#61a99b" Offset="1" />
</LinearGradientBrush>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Label Content="Notes" HorizontalAlignment="Left" Margin="8,8,0,0" VerticalAlignment="Top" FontSize="16" FontWeight="Bold"/>
<DataGrid x:Name="dgNotes" Margin="8,50,8,0" VerticalAlignment="Top" Height="121.28" d:LayoutOverrides="HorizontalAlignment" Background="#FFE4EEF3" AutoGenerateColumns="False" HorizontalGridLinesBrush="{x:Null}" VerticalGridLinesBrush="{x:Null}" ColumnWidth="Auto" CanUserDeleteRows="False" CanUserAddRows="False" CanUserReorderColumns="False" CanUserResizeRows="False" IsReadOnly="True" SelectionMode="Single" SelectionChanged="dgNotes_SelectionChanged" RowHeaderWidth="0" BorderBrush="{x:Null}" SelectedIndex="0">
<DataGrid.Columns>
<DataGridTextColumn x:Name="nDate" Header="Date"/>
<DataGridTextColumn x:Name="nEmployee" Header="Employee"/>
<DataGridTextColumn x:Name="nText" Header="Note" Width="*"/>
</DataGrid.Columns>
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Padding" Value="6" />
<Setter Property="BorderBrush" Value="#489283" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Background" Value="{StaticResource DataGridHeaderBackgroundBrush}" />
</Style>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="Transparent" />
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#66D7DFEC" />
</Trigger>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="#DDD7DFEC" />
</Trigger>
</Style.Triggers>
</Style>
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Margin" Value="3" />
<Setter Property="Padding" Value="3" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="Black" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
<Grid Margin="8,175.28,8,8" >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="25.96"/>
<RowDefinition Height="Auto" MinHeight="25.96"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" MinWidth="61.757"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Label Content="Date" d:LayoutOverrides="Height" HorizontalAlignment="Left"/>
<Label Content="Employee" Grid.Row="1" d:LayoutOverrides="Width"/>
<Label Content="Notes" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Top"/>
<Label x:Name="lblDate" Content="lblDate" Grid.Column="1" HorizontalAlignment="Left" d:LayoutOverrides="Height"/>
<Label Content="lblEmployee" Grid.Column="1" HorizontalAlignment="Left" Grid.Row="1" d:LayoutOverrides="Height" Name="lblEmployee" />
<TextBox x:Name="txtNotes" Grid.Column="1" Grid.Row="2" TextWrapping="Wrap" d:LayoutOverrides="Width" LostFocus="txtNotes_LostFocus" />
</Grid>
<Button x:Name="btnDelete" HorizontalAlignment="Right" Margin="0,8,8,0" VerticalAlignment="Top">
</Button>
<Button x:Name="btnAdd" HorizontalAlignment="Right" Margin="0,8,50,0" VerticalAlignment="Top" Click="btnAdd_Copy_Click">
</Button>
</Grid>
</UserControl>
User Control CS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
namespace WinformsWPF
{
/// <summary>
/// Interaction logic for ucNotes.xaml
/// </summary>
public partial class ucNotes : UserControl
{
List<noteItem> notes;
List<string> noteColumns;
public ucNotes()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
notes = new List<noteItem>
{
new noteItem{nID = 0, nDate = Convert.ToDateTime("9/5/2012 2:48 PM"), nEmployee = "Gardner, John", nText = "This is a test note"},
new noteItem{nID = 1, nDate = Convert.ToDateTime("9/5/2012 2:51 PM"), nEmployee = "Gardner, John", nText = "This is another test note. This test note is very long. It should cause overlap."}
};
noteColumns = new List<string> { "nDate", "nEmployee", "nText" };
dgNotes.GenerateColumns(noteColumns);
dgNotes.ItemsSource = notes;
}
private void dgNotes_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
noteItem noteInfo = (noteItem)dgNotes.SelectedItem;
lblDate.Content = noteInfo.nDate;
lblEmployee.Content = noteInfo.nEmployee;
txtNotes.Text = noteInfo.nText;
}
private void txtNotes_LostFocus(object sender, RoutedEventArgs e)
{
((noteItem)dgNotes.SelectedItem).nText = txtNotes.Text;
dgNotes.Items.Refresh();
}
private void btnAdd_Copy_Click(object sender, RoutedEventArgs e)
{
notes.Add(new noteItem { nDate = DateTime.Now, nEmployee = "Gardner, John" });
dgNotes.SelectedIndex = dgNotes.Items.Count - 1;
dgNotes.Items.Refresh();
txtNotes.Focus();
}
}
public static class dataGridExtension
{
public static void GenerateColumns(this DataGrid dataGrid, List<string> columns)
{
List<DataGridColumn> oldColumns = dataGrid.Columns.ToList();
dataGrid.Columns.Clear();
int index = 0;
foreach (var column in oldColumns)
{
var newCol = (DataGridTextColumn)column;
newCol.Binding = new Binding(columns[index++]);
dataGrid.Columns.Add(newCol);
}
}
}
public class noteItem
{
public int nID { set; get; }
public DateTime nDate { set; get; }
public string nEmployee { set; get; }
public string nText { set; get; }
}
}
The windows form is empty except for the ElementHost (ie, it's a fresh project with nothing changed). Let me know if you would like that code as well.
Any help would be appreciated.
You inadvertently had IsHitTestVisible="False" set on the UserControl....take that off and it works.
That will stop the control getting mouse messages when hit.
http://msdn.microsoft.com/en-us/library/system.windows.uielement.ishittestvisible.aspx
I have encountered this issue too, however the accepted solution did not work. For posterity I will add what I found out here.
TLDR
Disable XAML Hot Reload in Visual Studio.
Debug -> Options -> Debugging -> Hot Reload -> Uncheck Enable XAML Hot Reload
Problem
I have a WPF UserControl hosted in a WinForms project in an ElementHost control. The WPF UserControl contains a number of labels and textboxes along with two buttons, Save and Cancel.
The problem is fairly similar to OPs: You can navigate the UserControl with the tab key and can fire the Click event on the buttons with the space bar. Hovering over the buttons with the mouse causes the focus color to flash almost imperceptibly and the click event will only be raised if you happen to click at the exact moment the Button has focus.
It looks like something on the UserControl is "stealing" back focus after mouse movement ends, and apparently buttons can only be clicked if they have focus in WPF. That something turns out to be the debugger and Hot Reload.
Another hint that this problem can also be caused by the debugger is that disabling Hot Reload also solved another issue I had: the WinForms components of the project will loose their DPI-awareness and cause all sorts of wonky issues with presentation.
Edit: not caused by Hot Reload. Everything else is as described.
For reference I am using Visual Studio Professional 2019 (Version 16.8.2)
Related
I have a an app that will open many windows, and I want all windows to look the same. I am overriding the default Windows window chrome style and making my own, so any new window that is opened (excluding messageboxes) should have the same window style. However, no matter what I seem to try it does not work. I can get it to work with one window, but when I want to make it a global style it always crashes or simply doesn't work as it should.
Here is my code:
WindowBaseStyle.xaml
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Styles"
xmlns:views="clr-namespace:Myproject.Views">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="GlobalStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type views:WindowBase}" BasedOn="{StaticResource {x:Type Window}}">
<Setter Property="AllowsTransparency" Value="False" />
<Setter Property="BorderBrush" Value="Red" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="WindowState" Value="Normal" />
<Setter Property="WindowStyle" Value="SingleBorderWindow" />
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="30"
UseAeroCaptionButtons="False"/>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type views:WindowBase}">
<Border BorderBrush="Blue" BorderThickness="1" SnapsToDevicePixels="True">
<DockPanel Background="White" LastChildFill="True" >
<Grid Background="Blue" DockPanel.Dock="Top">
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal" VerticalAlignment="Center">
<Button Name="PART_SystemMenuButton" Command="{Binding MenuCommand}" Style="{DynamicResource SystemIconButton}">
<Image Height="16" Width="16" Source="/Resources/icon.png" Stretch="Fill"/>
</Button>
<Viewbox Height="16" HorizontalAlignment="Stretch" Margin="14,2,0,0" >
<TextBlock FontSize="12" Foreground="White" Text="{Binding Title,RelativeSource={RelativeSource FindAncestor,AncestorType=Window}}" />
</Viewbox>
</StackPanel>
<StackPanel Orientation="Horizontal" WindowChrome.IsHitTestVisibleInChrome="True">
<Button x:Name="PART_MinimizeButton" Command="{Binding MinimizeCommand}" Height="30" Margin="0,0,0,0" ToolTip="Minimize" Width="45">
<Image Source="/Resources/minimize.png" Stretch="None" />
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#0079CB" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#64AEEC"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button x:Name="PART_MaximizeButton" Command="{Binding MaximizeCommand}" Height="30" Margin="0,0,0,0" Width="45">
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Normal">
<Setter Property="Source" Value="/Resources/maximize.png" />
<Setter Property="Stretch" Value="None" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
<Setter Property="Source" Value="/Resources/unmaximize.png" />
<Setter Property="Stretch" Value="None" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#0079CB" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#64AEEC"/>
</Trigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Normal">
<Setter Property="ToolTip" Value="Maximize" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}, Path=WindowState}" Value="Maximized">
<Setter Property="ToolTip" Value="Restore Down" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
<Button x:Name="PART_CloseButton" Command="{Binding CloseCommand}" Height="30" Margin="0,0,0,0" ToolTip="Close" Width="45" >
<Image Source="/Resources/close.png" Stretch="None" />
<Button.Style>
<Style TargetType="{x:Type Button}">
<Setter Property="Background" Value="#0079CB" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
</StackPanel>
</Grid>
<!-- this ContentPresenter automatically binds to the content of the window -->
<ContentPresenter />
</DockPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
WindowBase.cs
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
namespace MyProject.Views
{
[TemplatePart(Name = "PART_MinimizeButton", Type = typeof(Button))]
[TemplatePart(Name = "PART_MaximizeButton", Type = typeof(Button))]
[TemplatePart(Name = "PART_CloseButton", Type = typeof(Button))]
[TemplatePart(Name = "PART_SystemMenuButton", Type = typeof(Button))]
public class WindowBase: Window
{
static WindowBase()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomWindow), new FrameworkPropertyMetadata(typeof(CustomWindow)));
}
public WindowBase()
{
Loaded += (sender, evnt) =>
{
var MinimizeButton = (Button)Template.FindName("PART_MinimizeButton", this);
var MaximizeButton = (Button)Template.FindName("PART_MaximizeButton", this);
var CloseButton = (Button)Template.FindName("PART_CloseButton", this);
var SystemMenuButton = (Button)Template.FindName("PART_SystemMenuButton", this);
MinimizeButton.Click += (s, e) => WindowState = WindowState.Minimized;
MaximizeButton.Click += (s, e) => WindowState = WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
CloseButton.Click += (s, e) => Close();
SystemMenuButton.Click += (s, e) => SystemCommands.ShowSystemMenu(this, GetMousePosition());
};
}
}
}
Window1.xaml
<local:WindowBase x:Class="MyProject.Views.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject.Views"
Height="750"
Width="1125">
<Grid>
</Grid>
</local:WindowBase>
Window1.xaml.cs
using System.Windows;
namespace MyProject.Views
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1: WindowBase
{
public Window1()
{
InitializeComponent();
}
}
}
I am overall following the MVVM pattern, and for the most part from all the articles and videos I have looked at online, they all follow this basic approach and they say it all works, but I can't seem to get it to work.
An additional note is that whenever I add my custom window control to the Window1.xaml file, it breaks the designer and says it is "Invalid Markup"
Also note I added my "WindowBaseStyle" resource dictionary to the App.xaml file as a merged resource dictionary.
Any help is greatly appreciated!! Thanks
Ok, as we discussed in the comments, it seems like the fastest solution to the problem you described is to use a StaticResource to get the window's style from a resource dictionary (or create an implicit style for windows.) I questioned the role of CustomWindow because I thought that might be causing problems with your default style override. (Remember: if you go the lookless control route and try to use the DefaultStyleKeyProperty override, you have to do this on every subclass of that control.)
However, I think doing something like this will get you reusable plumbing for multiple windows driven by viewmodels...
PopupHost
A class that would derive from your customized window. This code provides the following behaviors:
Allows the viewmodel to mark itself as having served its purpose, causing the window to close.
Allows attached properties to be specified by individual views that can affect the way the window appears on-screen, e.g. the window title.
Can be extended to notify presented items that the user has tried closing the window, allowing interception/cancellation or cleanup actions to be performed.
Code:
public class PopupHost : Window
{
private readonly AwaitableViewModelBase _viewModel;
public PopupHost(Window owner, AwaitableViewModelBase viewModel, string dataTemplateKey = null)
{
Owner = owner;
_viewModel = viewModel;
// Wrap the content in another presenter -- makes it a little easier to get to in order to look for attached properties.
var contentPresenter = new ContentPresenter
{
Content = viewModel
};
if (!string.IsNullOrWhiteSpace(dataTemplateKey))
contentPresenter.ContentTemplate = (DataTemplate) FindResource(dataTemplateKey);
Content = contentPresenter;
Task.Run(async () =>
{
await viewModel.Task;
Dispatcher.Invoke(Close);
});
Closed += ClosedHandler;
ApplyTemplate();
// Grab attached property values from the user control (or whatever element... you just need to find the descendant)
var contentElement = FindDescendantWithNonDefaultPropertyValue(contentPresenter, PopupWindowProperties.TitleProperty);
if (contentElement != null)
{
var binding = new Binding { Source = contentElement, Path = new PropertyPath(PopupWindowProperties.TitleProperty) };
SetBinding(TitleProperty, binding);
}
}
private void ClosedHandler(object sender, EventArgs args)
{
_viewModel?.Cancel();
Closed -= ClosedHandler;
}
private static Visual FindDescendant(Visual element, Predicate<Visual> predicate)
{
if (element == null)
return null;
if (predicate(element))
return element;
Visual foundElement = null;
(element as FrameworkElement)?.ApplyTemplate();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
{
var visual = VisualTreeHelper.GetChild(element, i) as Visual;
foundElement = FindDescendant(visual, predicate);
if (foundElement != null)
break;
}
return foundElement;
}
private static Visual FindDescendantWithNonDefaultPropertyValue(Visual element, DependencyProperty dp)
{
return FindDescendant(element, e => !(dp.GetMetadata(e).DefaultValue ?? new object()).Equals(e.GetValue(dp)));
}
}
PopupWindowProperties
Just a dumb object containing solely attached properties so your views can convey some information to the window.
public static class PopupWindowProperties
{
public static readonly DependencyProperty TitleProperty = DependencyProperty.RegisterAttached("Title", typeof(string), typeof(PopupWindowProperties), new FrameworkPropertyMetadata(string.Empty));
public static void SetTitle(UIElement element, string value) => element.SetValue(TitleProperty, value);
public static string GetTitle(UIElement element) => element.GetValue(TitleProperty) as string;
}
AwaitableViewModelBase
A simple abstract viewmodel which has a TaskCompletionSource. This allows the popup window and the viewmodel to coordinate closing.
public abstract class AwaitableViewModelBase : ViewModelBase
{
protected TaskCompletionSource<bool> TaskCompletionSource { get; set; }
public Task<bool> Task => TaskCompletionSource?.Task;
public void RegisterTaskCompletionSource(TaskCompletionSource<bool> tcs)
{
var current = TaskCompletionSource;
if (current != null && current.Task.Status == TaskStatus.Running)
throw new InvalidOperationException();
TaskCompletionSource = tcs;
}
public virtual void Cancel() => SetResult(false);
protected void SetResult(bool result) => TaskCompletionSource?.TrySetResult(result);
}
WindowService
Last but not least, the simple service that can present the requested view and viewmodel. You can use implicit DataTemplates for your viewmodels, or provide the specific x:Key value of the template you wish to use. Note that the await doesn't really do anything here because ShowDialog blocks. We return the bool since it can be used to easily identify if the user hit OK or Cancel on a modal.
public class WindowService
{
public async Task<bool> ShowModalAsync(AwaitableViewModelBase viewModel, string dataTemplateKey = null)
{
var tcs = new TaskCompletionSource<bool>();
viewModel.RegisterTaskCompletionSource(tcs);
Application.Current.Dispatcher.Invoke(() =>
{
var currentWindow = Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive) ?? Application.Current.MainWindow;
var window = new PopupHost(currentWindow, viewModel, dataTemplateKey);
window.ShowDialog();
});
return await viewModel.Task;
}
}
I would like to slide the text (marquee text) of the selected item in a combobox, if it's lenght is bigger than the width of the combobox. It can be either automatical or when the user put the mouse over the combobox. The problem is that i have absolutely no idea on how to do that. It's maybe possible to do that with a render transform (previous definition of a textblock inside it)? or with a storyboard?
Here is the xaml that i need to modify
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="ContentTemplate" >
<Setter.Value>
<DataTemplate DataType="DataGridColumnHeader" >
<ComboBox ItemContainerStyle="{StaticResource SingleSelectionComboBoxItem}" DisplayMemberPath="Oggetto" Width="100" Height="20" ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},Path=DataContext.Selezione, UpdateSourceTrigger=LostFocus}" SelectionChanged="SingleSelectionComboBox_SelectionChanged"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
EDIT: the problem is that i don't know which properties should i target in the storyboard
EDIT2: i took the template of the combobox and modified the text box section like this :
<Style x:Key="ComboBoxEditableTextBox" TargetType="{x:Type TextBox}">
<Style.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="0" To="100" Duration="00:00:10" Storyboard.TargetProperty="X" Storyboard.TargetName="transferCurreny" RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform x:Name="transferCurreny" X="0"/>
</Setter.Value>
</Setter>
The problem is that this seems to have no effect
EDIT 3: i realized that i had to use the template that use the style i mentioned above
<ControlTemplate x:Key="ComboBoxEditableTemplate" TargetType="{x:Type ComboBox}">
<Grid x:Name="Placement" SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Popup x:Name="PART_Popup" AllowsTransparency="true" Grid.ColumnSpan="2"
IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}}"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"
Placement="Bottom">
<Themes:SystemDropShadowChrome x:Name="Shdw" Color="Transparent"
MaxHeight="{TemplateBinding MaxDropDownHeight}"
MinWidth="{Binding ActualWidth, ElementName=Placement}">
<Border x:Name="DropDownBorder"
BorderBrush="{DynamicResource {x:Static SystemColors.WindowFrameBrushKey}}"
BorderThickness="1" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}">
<ScrollViewer x:Name="DropDownScrollViewer">
<Grid RenderOptions.ClearTypeHint="Enabled">
<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>
</Border>
</Themes:SystemDropShadowChrome>
</Popup>
<Themes:ListBoxChrome x:Name="Border" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}" Grid.ColumnSpan="2"
RenderMouseOver="{TemplateBinding IsMouseOver}"
RenderFocused="{TemplateBinding IsKeyboardFocusWithin}" />
<TextBox x:Name="PART_EditableTextBox"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"
Margin="{TemplateBinding Padding}" Style="{StaticResource ComboBoxEditableTextBox}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" >
</TextBox>
<ToggleButton Grid.Column="1"
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay, RelativeSource={RelativeSource TemplatedParent}}"
Style="{StaticResource ComboBoxToggleButton}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="true">
<Setter Property="Foreground" Value="Black" />
</Trigger>
<Trigger Property="IsDropDownOpen" Value="true">
<Setter Property="RenderFocused" TargetName="Border" Value="true" />
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Height" TargetName="DropDownBorder" Value="95" />
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
<Setter Property="Background" Value="#FFF4F4F4" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true" />
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false" />
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false" />
</MultiTrigger>
<Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
<Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5" />
<Setter Property="Color" TargetName="Shdw" Value="#71000000" />
</Trigger>
<Trigger Property="ScrollViewer.CanContentScroll" SourceName="DropDownScrollViewer" Value="false">
<Setter Property="Canvas.Top" TargetName="OpaqueRect"
Value="{Binding VerticalOffset, ElementName=DropDownScrollViewer}" />
<Setter Property="Canvas.Left" TargetName="OpaqueRect"
Value="{Binding HorizontalOffset, ElementName=DropDownScrollViewer}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
the textbox section is the one that use the style. However when i do Template="{StaticResource ComboBoxEditableTemplate}" in the combobox, i cannot see the selected item anymore, even if it's not null
Updated 08/02
Basically in order to avoid the animation issues you are facing - you will need to add a ScrollViewer as a wrapper to the content-site or selection-box TextBlock. You can either do that by using a custom template selector for your ComboBox so as defined below, or provide a custom item-template with ScollViewer.
Secondly, you can either create a custom ComboBox control in C# to bind the above template-selector/item-template and apply slide-animation on MouseEnterEvent; or implement the same in XAML itself.
For this example - have implemented the control in C#, and provided
a sample usage in XAML.
Code for custom control, and template selector
public class SlidingComboBox : ComboBox
{
public static readonly DependencyProperty SlideForeverProperty = DependencyProperty.Register("SlideForever", typeof(bool), typeof(SlidingComboBox), new FrameworkPropertyMetadata(false));
public bool SlideForever
{
get { return (bool)GetValue(SlideForeverProperty); }
set { SetValue(SlideForeverProperty, value); }
}
protected ContentPresenter _parent;
protected DoubleAnimation _animation;
protected TranslateTransform _translate;
protected Storyboard _storyBoard;
public SlidingComboBox()
{
Loaded += ExComboBox_Loaded;
ClipToBounds = true;
//assign template selector - just to re-template ContentSite / selection box
//uncomment this code - if you want to default-template for popup-items
//ItemTemplateSelector = new SlidingComboBoxItemTemplateSelector();
}
private void ExComboBox_Loaded(object sender, RoutedEventArgs e)
{
Loaded -= ExComboBox_Loaded;
//get content-site holder/parent
_parent = this.GetChildOfType<ContentPresenter>();
//setup slide animation
_animation = new DoubleAnimation()
{
From = 0,
RepeatBehavior = SlideForever ? RepeatBehavior.Forever : new RepeatBehavior(1), //repeat only if slide-forever is true
AutoReverse = SlideForever
};
//create storyboard
_storyBoard = new Storyboard();
_storyBoard.Children.Add(_animation);
Storyboard.SetTargetProperty(_animation, new PropertyPath("RenderTransform.(TranslateTransform.X)"));
}
protected override void OnMouseEnter(MouseEventArgs e)
{
//get actual textblock that renders the selected value
var textBlock = _parent.GetChildOfType<TextBlock>();
//and translate-transform for animation
textBlock.RenderTransform = _translate = new TranslateTransform();
//start animation only if text-block width is greater than parent
if (_parent.ActualWidth < textBlock.ActualWidth)
{
_animation.Duration = TimeSpan.FromMilliseconds(((int)textBlock.Text?.Length * 100));
_animation.To = _parent.ActualWidth - textBlock.ActualWidth;
_storyBoard.Begin(textBlock);
}
base.OnMouseEnter(e);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
//stop animation once mouse pointer is off the control
_storyBoard.Stop();
//reset render state
var textBlock = _parent.GetChildOfType<TextBlock>();
textBlock.RenderTransform = _translate = new TranslateTransform();
base.OnMouseLeave(e);
}
}
public class SlidingComboBoxItemTemplateSelector : DataTemplateSelector
{
DataTemplate _selectedItemTemplate;
public SlidingComboBoxItemTemplateSelector()
{
//create datatemplate with ScrollViewer and TextBlock as child
var textBlock = new FrameworkElementFactory(typeof(TextBlock));
textBlock.SetValue(TextBlock.TextWrappingProperty, TextWrapping.NoWrap);
textBlock.SetBinding(TextBlock.TextProperty, new Binding("SelectedValue")
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.FindAncestor, AncestorType = typeof(ComboBox) }
});
var scrollViewer = new FrameworkElementFactory(typeof(ScrollViewer));
scrollViewer.SetValue(ScrollViewer.CanContentScrollProperty, true);
scrollViewer.SetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Hidden);
scrollViewer.SetValue(ScrollViewer.VerticalScrollBarVisibilityProperty, ScrollBarVisibility.Disabled);
scrollViewer.AppendChild(textBlock);
_selectedItemTemplate = new DataTemplate
{
VisualTree = scrollViewer
};
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ComboBoxItem comboBoxItem = container.GetVisualParent<ComboBoxItem>();
if (comboBoxItem == null)
{
//send back only if template requested for ContentSite, and not for combo-box item(s)
return _selectedItemTemplate;
}
return null;
}
}
/// <summary>
/// VisualTree helper
/// </summary>
public static class HelperExtensions
{
public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
public static T GetVisualParent<T>(this DependencyObject child) where T : Visual
{
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
}
Sample usage:
<Window x:Class="MarqueeSample.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:MarqueeSample"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="200" Width="400">
<Grid Margin="20">
<DataGrid AutoGenerateColumns="False" IsReadOnly="True" Width="225" VerticalAlignment="Center">
<DataGrid.ItemsSource>
<col:Hashtable>
<col:ArrayList x:Key="TestData1">
<sys:String>Tiny</sys:String>
<sys:String>Keep calm and love programming</sys:String>
</col:ArrayList>
<col:ArrayList x:Key="TestData2">
<sys:String>Sample string</sys:String>
<sys:String>Another string to test</sys:String>
</col:ArrayList>
</col:Hashtable>
</DataGrid.ItemsSource>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="100" Binding="{Binding Key}" />
<DataGridTemplateColumn Header="Value" Width="100">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:SlidingComboBox ItemsSource="{Binding Value}">
<local:SlidingComboBox.ItemTemplate>
<DataTemplate>
<ScrollViewer CanContentScroll="True" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Disabled">
<TextBlock Text="{Binding}" />
</ScrollViewer>
</DataTemplate>
</local:SlidingComboBox.ItemTemplate>
</local:SlidingComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
You can also use SlideForever property to manipulate RepeatBehaviour.
<local:SlidingComboBox ItemsSource="{Binding Value}" SlideForever="True" />
I've been looking into this and I think I have your solution. You should combine both a RenderTransform and a Storyboard on the ComboBox ContentPresenter (this is what displays the currently selected item)
<ComboBox Grid.Row="1" Name="MyComboBox" Width="200">
<ComboBox.Resources>
<Style TargetType="ContentPresenter">
<Setter Property="RenderTransform">
<Setter.Value>
<TranslateTransform X="0" Y="0" />
</Setter.Value>
</Setter>
<Style.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<EventTrigger.Actions>
<BeginStoryboard x:Name="ScrollItem">
<Storyboard RepeatBehavior="Forever">
<DoubleAnimation Duration="00:00:5" From="0" To="200" Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)" />
<DoubleAnimation Duration="00:00:5" BeginTime="00:00:5" From="-200" To="0" Storyboard.TargetProperty="RenderTransform.(TranslateTransform.X)" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</ComboBox.Resources>
<ComboBoxItem>
I am combobox value 1
</ComboBoxItem>
<ComboBoxItem>
I am combobox value 2, Hello!
</ComboBoxItem>
</ComboBox>
Having the ComboBox of size 200, scrolling from 0 to 200, then -200 to 0, scrolls the text off the right hand side of the control, and in the left hand side. (You can drop the 2nd DoubleAnimation if you like and set AutoReverse to True to cause the text to bounce back in if you'd rather that. This does not code you around items that are too big for the control, you will need to write some code for the ComboBox so it decides if the currently selected Item is too big, and from code behind (or maybe a custom ComboBox Class) dynamically turn on/off the storyboard.
First iterate through all items of your combobox, check for the width of every items by assigning the text to a label. Then, check width every time, if width of current item gets greater than previous items then change the maximum width.
int DropDownWidth(ComboBox myCombo)
{
int maxWidth = 0;
int temp = 0;
Label label1 = new Label();
foreach (var obj in myCombo.Items)
{
label1.Text = obj.ToString();
temp = label1.PreferredWidth;
if (temp > maxWidth)
{
maxWidth = temp;
}
}
label1.Dispose();
return maxWidth;
}
private void window_loaded(object sender, EventArgs e)
{
comboBox1.DropDownWidth = DropDownWidth(comboBox1);
}
OR
int DropDownWidth(ComboBox myCombo)
{
int maxWidth = 0, temp = 0;
foreach (var obj in myCombo.Items)
{
temp = TextRenderer.MeasureText(obj.ToString(), myCombo.Font).Width;
if (temp > maxWidth)
{
maxWidth = temp;
}
}
return maxWidth;
}
Thanks to those guys got it from here --> Auto-width of ComboBox's content
I have a datagrid which contains hyperlinks in a DatagridTemplateColumn like the following :
<DataGridTemplateColumn Width="170" SortMemberPath="Joueur.EtatCivil.Joueur_nom" CanUserReorder="False" CanUserResize="True" Header="">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate />
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource DatagridCellHyperlinkStyle}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Padding="{TemplateBinding Padding}" Width="Auto" VerticalAlignment="Center" SnapsToDevicePixels="True">
<TextBlock Foreground="{TemplateBinding Foreground}" Width="Auto" TextTrimming="CharacterEllipsis">
<Hyperlink IsEnabled="{TemplateBinding IsEnabled}">
<InlineUIContainer TextDecorations="{Binding Path=TextDecorations, RelativeSource={RelativeSource AncestorType=TextBlock}}" Foreground="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType=TextBlock}}">
<ContentPresenter Content="{Binding DataContext.Joueur.EtatCivil.NomComplet, RelativeSource={RelativeSource AncestorType=DataGridRow}}"/>
</InlineUIContainer>
<Hyperlink.Style>
<Style TargetType="Hyperlink" BasedOn="{StaticResource HyperlinkStyle}">
<EventSetter Event="Hyperlink.Click" Handler="ClickEvent" />
<EventSetter Event="Hyperlink.MouseEnter" Handler="MouseOverEvent" />
<EventSetter Event="Hyperlink.MouseLeave" Handler="ClicJoueurMouseLeaveEvent" />
</Style>
</Hyperlink.Style>
</Hyperlink>
</TextBlock>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGridTemplateColumn.CellStyle>
</DataGridTemplateColumn>
This works fine and as expected but I have trouble when I just move my cursor hover many lines in a small time (<0.5s maybe).
My MouseEnterEvent calls a method named "Show();" and my MouseLeaveEvent a method named "Hide();"
This show/hide a small popup to display to the user more data. The problem is that my show(); method update a Wpf toolkit chart which seems to have trouble to be updated with this frequency (10 times in 0.5s). The binding seems to "crash" and never works again.
this helped me : Binding update adds news series to WPF Toolkit chart (instead of replacing/updating series) )
I update my chart like this :
this.colonnes.ItemsSource = null;
_DataGraph = new ObservableCollection<GraphiqueValeurs>();
_DataGraph.Add(new GraphiqueValeurs(value, "my string");
this.colonnes.ItemsSource = _DataGraph;
this.colonnes.UpdateLayout();
With the following xaml :
<chartingToolkit:Chart VerticalAlignment="Top" HorizontalAlignment="Center" FontFamily="/BPM2015;component/#Open Sans Condensed" BorderBrush="Transparent" Name="columnChart" Title="Column Series Demo" Foreground="{DynamicResource CouleurTexte}" >
<chartingToolkit:ColumnSeries Name="colonnes" Foreground="{DynamicResource CouleurTexte}" DependentValueBinding="{Binding Valeur}"
IndependentValueBinding="{Binding Name}"
DataPointStyle="{StaticResource ColorByPreferenceColumn}"
>
<chartingToolkit:ColumnSeries.DependentRangeAxis>
<chartingToolkit:LinearAxis Orientation="Y" Minimum="0" Maximum="20" Title="" Foreground="Transparent" ShowGridLines="True">
<chartingToolkit:LinearAxis.MajorTickMarkStyle>
<Style TargetType="Line">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</chartingToolkit:LinearAxis.MajorTickMarkStyle>
</chartingToolkit:LinearAxis>
</chartingToolkit:ColumnSeries.DependentRangeAxis>
</chartingToolkit:ColumnSeries>
<chartingToolkit:Chart.LegendStyle>
<Style TargetType="Control">
<Setter Property="Width" Value="0" />
<Setter Property="Height" Value="0" />
</Style>
</chartingToolkit:Chart.LegendStyle>
<chartingToolkit:Chart.TitleStyle>
<Style TargetType="Control">
<Setter Property="Width" Value="0" />
<Setter Property="Height" Value="0" />
</Style>
</chartingToolkit:Chart.TitleStyle>
So my question is : How I can avoid all these quick mouseEnter ? I'd like to avoid my mouseEnter function to call the "Show()" method if the cursor doesn't stay at least 0.4s on the hyperlink.
How could I do that ?
This
Thank you
You can use a DispatcherTimer to add a delay to calling your function and combine that with a bool flag that is set to true in the MouseEnter handler and set to false in the MouseLeave handler. Try this:
private DispatcherTimer timer = new DispatcherTimer();
private isMouseOver = false;
...
timer.Interval = TimeSpan.FromMilliseconds(400);
timer.Tick += Timer_Tick;
...
private void MouseEnterHandler(object sender, MouseEventArgs e)
{
isMouseOver = true;
timer.Start();
}
private void MouseLeaveHandler(object sender, MouseEventArgs e)
{
isMouseOver = false;
timer.Stop();
}
private void Timer_Tick(object sender, EventArgs e)
{
if (isMouseOver) Show();
timer.Stop();
}
I'm using WPF for my very simple project. I've created login page using Page class and there are two controls there. They are Button and TextBox. Then I'm using this page in main window through the Frame. In main window I'm trying to add event handler to the Button on 'Click' event and somewhy it doesn't work. There are no error messages or somthing else. I'he tried to debug but it just does not enter the method.
Here is the Page code:
<Page x:Class="BJack.LoginPage"
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"
Title="LoginPage">
<Page.Resources>
<Style TargetType="TextBox" x:Key="txtHint">
<Style.Triggers>
<Trigger Property="IsKeyboardFocused" Value="False">
<Setter Property="Text" Value="Login" />
<Setter Property="Foreground" Value="#FFB8A8A8" />
</Trigger>
</Style.Triggers>
<Style.Setters>
<Setter Property="Height" Value="25" />
<Setter Property="FontSize" Value="16" />
<Setter Property="Width" Value="300" />
</Style.Setters>
</Style>
</Page.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<StackPanel VerticalAlignment="Center">
<TextBox Name="txtLogin" Style="{StaticResource ResourceKey=txtHint}"></TextBox>
<Button Grid.Row="1" Height="30" Width="150" Margin="0 30 0 0" Name="enterButton">Enter</Button>
</StackPanel>
</Grid>
</Page>
And here is a MainWindow code:
public partial class MainWindow : Window
{
LoginPage lPage = new LoginPage();
public MainWindow()
{
InitializeComponent();
InitializeProgramm();
}
private void InitializeProgramm()
{
mainFrame.NavigationService.Navigate(new Uri("LoginPage.xaml", UriKind.Relative));
lPage.enterButton.Click += enterButton_Click;
}
void enterButton_Click(object sender, RoutedEventArgs e)
{
if (lPage.txtLogin.Text.Length > 0)
{
mainFrame.Navigate(new Uri("GamePage.xaml", UriKind.Relative));
this.WindowState = System.Windows.WindowState.Maximized;
}
}
private void mainWin_MouseDown(object sender, MouseButtonEventArgs e)
{
Keyboard.ClearFocus();
}
}
here is a solution
private void InitializeProgramm()
{
mainFrame.NavigationService.Navigate(lPage);
lPage.enterButton.Click += enterButton_Click;
}
this will set the instance lPage as the content of the mainFrame on which you've attached the event handler instead of a newly created instance
what is the best practice to show text label (e.g. "Name") with the TextBox in WPF?
I want a label "Name" above the TextBox and many similar Labels/TextBoxes.
Should I put the pairs Label/TextBox into the vertical StackPanel?
Is there a simpler solution?
Here is a control that does it:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
public class KeyValueControl : Control
{
public static readonly DependencyProperty KeyProperty = DependencyProperty.Register(
"Key",
typeof(string),
typeof(KeyValueControl),
new PropertyMetadata(default(string)));
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
"Value",
typeof(object),
typeof(KeyValueControl),
new FrameworkPropertyMetadata
{
DefaultValue = null,
BindsTwoWayByDefault = true,
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
});
static KeyValueControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(KeyValueControl), new FrameworkPropertyMetadata(typeof(KeyValueControl)));
}
public string Key
{
get
{
return (string)GetValue(KeyProperty);
}
set
{
SetValue(KeyProperty, value);
}
}
public object Value
{
get
{
return GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
}
Style:
<Style TargetType="{x:Type local:KeyValueControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:KeyValueControl}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Key, RelativeSource={RelativeSource TemplatedParent}}"/>
<TextBox Grid.Column="1" Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Usage (create a property grid):
<ItemsControl>
<customControls:KeyValueControl Key="First" Value="{Binding Value1}" />
<customControls:KeyValueControl Key="Second" Value="{Binding Value2}" />
<customControls:KeyValueControl Key="Last" Value="{Binding Value3}" />
<customControls:KeyValueControl Key="Bool1" Value="{Binding Bool1}" Style="{StaticResource CheckBoxStyle}"/>
<customControls:KeyValueControl Key="Bool2" Value="{Binding Bool2}" Style="{StaticResource CheckBoxStyle}"/>
</ItemsControl>
It really depends on what you want to do with these controls in the future. If you want to reuse this kind of control multiple times (and maybe create it on the fly), it would be the best to create UserControl and program it. You can then easily reuse it in a very simple manner (like putting in on StackPanel).
Code for LabelTextBox.xaml
<UserControl x:Class="YourProject.LabelTextBox"
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="49" d:DesignWidth="314" MinHeight="49" MaxHeight="49">
<Grid>
<Label Content="Label" Height="28" HorizontalAlignment="Left" Name="BaseLabel" VerticalAlignment="Top" />
<TextBox Height="23" Margin="0,26,0,0" Name="BaseTextBox" VerticalAlignment="Top" />
</Grid>
</UserControl>
Code for LabelTextBox.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace YourProject
{
/// <summary>
/// Interaction logic for LabelTextBox.xaml
/// </summary>
public partial class LabelTextBox : UserControl
{
public LabelTextBox()
{
InitializeComponent();
}
string LocalLabel = "";
string LocalTextBox = "";
public string Label
{
get { return LocalLabel; }
set
{
LocalLabel = value;
BaseLabel.Content = value;
}
}
public string TextBox
{
get { return LocalTextBox; }
set
{
LocalTextBox = value;
BaseTextBox.Text = value;
}
}
}
}
You can change Label text and TextBox content with Label and TextBox property of new control (hidden in "Other" part of Properties in designer. You can also program additional functions for the UserControl.
If you don't need to reuse these controls so much, other solutions will suffice.
If you want the flexibility to manipulate this text label structure I suggest to wrap each TextBox and Label in a dock panel, and set the docking in a style that will apply on all labels and text boxes.
so it'll be like
<StackPanel>
<StackPanel.Resources>
<Style TargetType={x:Type Label}>
<Setter Property="DockPanel.Dock" Value="Top"/>
</Style>
</StackPanel.Resources>
<DockPanel>
<Label></Label>
<TextBox></TextBox>
</DockPanel>
<DockPanel>
<Label></Label>
<TextBox></TextBox>
</DockPanel>
</StackPanel>
I usually do something like this:
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Label Margin="5">Repository URL:</Label>
<TextBox Grid.Column="1" Margin="5"></TextBox>
</Grid>
</StackPanel>
If you did this often enough, you could create a UserControl or Datatemplate. But WPF is just a verbose markup language...
I use this
<Style TargetType="{x:Type TextBox}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TextBox}">
<Grid HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="ControlLabel" Grid.Column="0"
VerticalAlignment="Top" HorizontalAlignment="Left"
Text="{TemplateBinding Tag}" />
<Line x:Name="LineColumnLabel" Grid.Column="1" X1="0" X2="0" Y1="0" Y2="{TemplateBinding Height}"
Stroke="#B31C1C1C" StrokeThickness="0.5" Margin="5,0,5,0" />
<ScrollViewer Grid.Column="2" x:Name="PART_ContentHost"
IsTabStop="{TemplateBinding ScrollViewer.IsTabStop}"
TextElement.Foreground="{TemplateBinding Foreground}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="Tag" Value="{x:Null}">
<Setter TargetName="ControlLabel" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ControlLabel" Property="Margin" Value="0" />
<Setter TargetName="LineColumnLabel" Property="Visibility" Value="Collapsed" />
<Setter TargetName="LineColumnLabel" Property="Margin" Value="0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And
<Grid>
<TextBox Tag="The Name"/>
</Grid>
Create a class X that holds label and text that implements INotifyPropertyChanged. Make an ObservableCollection. This will be the ItemsSource for ListBox, ComboBox, StackPanel.. whatever you choose. Create a DataTemplate that displays X the way you want it to.