I have a situation as follows
I want to hide delete button when no item is selected & show when something is selected.
Also hide that button if user selects an item & clicks elsewhere (which represents that user is no longer working with the listbox).
I tried LostFocus events, checking SelectedIndex etc. but no success. Any idea how to do this?
private void ListBoxItem_LostFocus(object sender, RoutedEventArgs e)
{
if (button.IsFocused != true) // checking if user has selected an item & clicking on button (valid action)
{
listbox.SelectedIndex = -1;
}
}
private void listbox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (listbox.SelectedIndex == -1)
{
button.Visibility = Visibility.Hidden;
}
else
{
button.Visibility = Visibility.Visible;
}
}
Joseph's answer is correct but you don't need a converter.
You can achieve the same result with a simple trigger:
<ListBox>
...
<ListBox.Style>
<Style TargetType="{x:Type ListBox}">
<Style.Triggers>
<Trigger Property="SelectedItem" Value="{x:Null}">
<Setter TargetName="YOUR_BUTTON'S_NAME" Property="Visibility" Value="Hidden"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.Style>
</ListBox>
Edit: It's indeed impossible to use TargetName in a Style Setter.
So, the closest solution I could make work is to create a DataTrigger:
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Top">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyList, Path=SelectedItem}"
Value="{x:Null}">
<Setter Property="Visibility" Value="Hidden"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
<TextBlock Text="Button"/>
</Button>
<ListBox x:Name="MyList">
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
<TextBlock Text="Something"/>
</ListBox>
</DockPanel>
For the first half of the question the best way is to use a converter like so :
1. add a Converter class :
public class VisibilityConverter:IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (value == null ? Visibility.Hidden : Visibility.Visible);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
2.add it to the Window resources:
<Window.Resources>
<wpfApplication:VisibilityConverter x:Key="VisibilityConverter"/>
</Window.Resources>
3. and use it to hide/show your button :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox HorizontalAlignment="Center" x:Name="listBox">
<ListBoxItem Content="Fist"/>
<ListBoxItem Content="Second"/>
<ListBoxItem Content="Third"/>
<ListBoxItem Content="Fourth"/>
</ListBox>
<Button Content="delete" Grid.Row="1" HorizontalAlignment="Center" Visibility="{Binding SelectedItem,ElementName=listBox,Converter={StaticResource VisibilityConverter}}"/>
</Grid>
And for the second half of the question, you must handle the MouseDown event in your window:
MouseDown="MainWindow_OnMouseDown"
and add the following handler (that will reset the SelectedItem of the list to null)
private void MainWindow_OnMouseDown(object sender, MouseButtonEventArgs e)
{
listBox.SelectedItem = null;
}
Run this code separately and try.it is working for me here.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel>
<ListBox x:Name="lst">
<ListBox.Triggers>
<EventTrigger RoutedEvent="GotFocus">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="DeleteButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.1" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="LostFocus">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="DeleteButton" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0:0:0.1" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ListBox.Triggers>
<ListBoxItem>ListBoxItem1</ListBoxItem>
<ListBoxItem>ListBoxItem2</ListBoxItem>
<ListBoxItem>ListBoxItem3</ListBoxItem>
<ListBoxItem>ListBoxItem4</ListBoxItem>
</ListBox>
<Button x:Name="DeleteButton" Content="Delete" Height="30" Width="100">
<Button.Style>
<Style BasedOn="{StaticResource alredayDefinedStyleKey}" TargetType="Button">
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=SelectedItem, ElementName=lst}" Value="{x:Null}" >
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<ListBox Grid.Column="1">
<ListBoxItem>1</ListBoxItem>
<ListBoxItem>2</ListBoxItem>
</ListBox>
</Grid>
LostFocus will only fire, if the element loses Logical focus. Use LostKeyboardFocus event. Then you're one step closer.
Related
I have a WPF application where I need to use Tab Control. In my Tab Control I have total three tab items.
Here my MainWindow.xaml code :
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl
Grid.Row="0"
x:Name="TestTabs">
<TabItem Header="Section 1"/>
<TabItem Header="Section 2"/>
<TabItem Header="Section 3"/>
</TabControl>
</Grid>
Due to my design purpose I need to underline the TabItem whenever it is selected. To give more design to the UI, I add some Data trigger animation into it. My approach is, I give two rectangles under the first and second Tab item and bind With Tab Control selected index. So, every time any individual Tab item is selected the Enter animation and Exit animation will fired/execute. I do not give any rectangle under the third tab item because the previous two rectangles will cover tho whole animation of those three tab items and it's sufficient.
But the problem is, the Rectangles Enter animations and Exit animations are interfere with each other and both are execute at a same time when I click any specific tab item bounded to the tab control selected index.
To overcome this issue I implement a View Model. Using this View Model I can track the last two tab items which I selected. To verify that my View Model is perfectly working or not. I tested with a Button. If I click the button then a Message Box will appear and it will show my current and Previous tab item which I selected.
But I don't know How to hide and show Rectangles depending on the last two tab items which I selected through the View Model. My approach is only to show the rectangles that are bounded to their Tab items. I give an example suppose the first Tab item named "Section 1" has a Rectangle named Rect1 and the second Tab item named "Section 2" has a Rectangle named Rect2. this Rect1 and Rect2 are only visible if their parent tab item is selected through under the View Model.
Here is my Complete MainWindow.xaml code :
<Window.Resources>
<local:TestViewModel x:Key="MainViewModel"/>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<TabControl DataContext="{StaticResource MainViewModel}"
SelectedIndex="{Binding Selected}"
Grid.Row="0"
x:Name="TestTabs">
<TabItem Header="Section 1"/>
<TabItem Header="Section 2"/>
<TabItem Header="Section 3"/>
</TabControl>
<Button Content="Check
Selected Index"
Grid.Row="1"
x:Name="TestButton"
Click="TestButton_OnClick"/>
<DockPanel x:Name="rp" Grid.Row="0" LastChildFill="False" HorizontalAlignment="Stretch">
<Canvas DockPanel.Dock="Left" >
<Rectangle x:Name="Rect1" Fill="#ff0000" VerticalAlignment="Top" Width="50.2" Height="4" Margin="6,25,0,0" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.EdgeMode="Aliased" RenderOptions.BitmapScalingMode="HighQuality" >
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="1">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible" />
<Setter Property="IsEnabled" Value="True" />
<DataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard3"/>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard4"/>
<BeginStoryboard Name="MyBeginStoryboard1">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="63.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard2">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="63.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="2"/>
</MultiDataTrigger.Conditions>
<MultiDataTrigger.EnterActions>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard1"/>
<RemoveStoryboard BeginStoryboardName="MyBeginStoryboard2"/>
<BeginStoryboard Name="MyBeginStoryboard3">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="123.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.EnterActions>
<MultiDataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard4">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="123.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</MultiDataTrigger.ExitActions>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Rectangle x:Name="Rect2" Fill="#ff0000" VerticalAlignment="Top" Width="50.2" Height="4" Margin="68,25,0,0" SnapsToDevicePixels="True" UseLayoutRounding="True" RenderOptions.EdgeMode="Aliased" RenderOptions.BitmapScalingMode="HighQuality" >
<Rectangle.Style>
<Style TargetType="{x:Type Rectangle}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TestTabs, Path=SelectedIndex}" Value="2">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="RenderOptions.EdgeMode" Value="Aliased"/>
<Setter Property="Visibility" Value="Visible" />
<Setter Property="IsEnabled" Value="True" />
<DataTrigger.EnterActions>
<BeginStoryboard Name="MyBeginStoryboard5">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="0" To="63.5" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Name="MyBeginStoryboard6">
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" From="63.5" To="0" Duration="0:0:0.2"></DoubleAnimation>
<DoubleAnimation Storyboard.TargetProperty="Width" From="50.2" To="50.2" Duration="0:0:0.2"></DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
</Canvas>
</DockPanel>
</Grid>
Here is my Main View Model code name as TestViewModel :
class TestViewModel : INotifyPropertyChanged
{
private int _selected;
public int Selected
{
get { return _selected; }
set
{
int temp = _selected;
_selected = value;
_previousSelected = temp;
NotifyPropertyChanged("Selected", temp, value);
NotifyPropertyChanged("PreviousSelected", temp, temp);
}
}
int _previousSelected = 0;
public int PreviousSelected
{
get { return _previousSelected; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged<T>(string propertyName, T oldvalue, T newvalue)
{
OnPropertyChanged(this, new PropertyChangedExtendedEventArgs<T>(propertyName, oldvalue, newvalue));
}
public virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(sender, e);
}
}
public class PropertyChangedExtendedEventArgs<T> : PropertyChangedEventArgs
{
public virtual T OldValue { get; private set; }
public virtual T NewValue { get; private set; }
public PropertyChangedExtendedEventArgs(string propertyName, T oldValue, T newValue)
: base(propertyName)
{
OldValue = oldValue;
NewValue = newValue;
}
}
Here is the MainWindow.xaml.cs logic code where I tested the View model with a Button and Message Box to see if the last two item selection is perfectly working or not.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void TestButton_OnClick(object sender, RoutedEventArgs e)
{
var vm = TestTabs.DataContext as TestViewModel;
MessageBox.Show(string.Format("You selected tab {0} ,previous selected tab{1}", vm.Selected, vm.PreviousSelected));
}
}
I don't know that my approach is correct or not that means hiding or showing the rectangles depending on that View Model is a good idea or not but I need some similar solution and it's may be different that means the rectangles are only trigger their animation depending on View model. But the answer is almost same. All type of suggestion are accepted.
I attach a GIF so that people understand that this effect I want to get in my TabControl on Tab Item selection. I know it can be done easily with code behind but I want to get solution in pure XAML and MVVM base.
So I created a UserControl, and I created a dependency property for it so I can use it for my animation.
The animation works, it sets off but when I click a new item it doesnt slide back in it jumps back to the start and then restarts.
How do I make the animation slide back in.
This is due to the true and false statements in the OnSelectionChanged.
<Grid.Resources>
<system:Double x:Key="SlideOffSet">50</system:Double>
<Storyboard x:Key="SlideRight">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
From="0" To="{StaticResource SlideOffSet}"
Duration="0:0:0.2" />
</Storyboard>
<Storyboard x:Key="SlideLeft">
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)"
From="{StaticResource SlideOffSet}" To="0"
Duration="0:0:0.2" />
</Storyboard>
</Grid.Resources>
<local:CustomizedList x:Name="uc" Margin="5" Grid.Column="0" DataContext="{Binding}" />
<StackPanel Grid.Column="2"
Width="50"
Height="50"
Background="Gray">
<StackPanel.Style>
<Style TargetType="StackPanel">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=uc, Path=SelectedItem}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource SlideRight}" />
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource SlideLeft}" />
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
<StackPanel.RenderTransform>
<TranslateTransform />
</StackPanel.RenderTransform>
</StackPanel>
UserControl
<ListBox ItemsSource="{Binding}" x:Name="list" HorizontalContentAlignment="Stretch" SelectionChanged="List_OnSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Margin="2" BorderThickness="2" CornerRadius="3" BorderBrush="Brown">
<TextBlock Text="{Binding}" Margin="1">
<TextBlock.Background>
<LinearGradientBrush>
<GradientStop Offset="0" Color="AliceBlue"/>
<GradientStop Offset="1" Color="Blue"/>
</LinearGradientBrush>
</TextBlock.Background>
</TextBlock>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And the SelectionChanged event for the Dependency Property
public static readonly DependencyProperty SelectedItemProperty =
DependencyProperty.Register("SelectedItem", typeof(bool), typeof(CustomizedList));
public bool SelectedItem
{
get { return (bool)GetValue(SelectedItemProperty); }
set { SetValue(SelectedItemProperty, value); }
}
private void List_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = false;
SelectedItem = true;
}
When the item is selected I want it to be true and when another item is selected, make it slide back and slide out again
Well, then you need to wait asynchronously for the animation to occur before you set the property back to true. Try this:
private async void List_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SelectedItem = false;
await Task.Delay(1000); //wait for a second
SelectedItem = true;
}
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'm trying to create a download bar like chrome.
The issue I'm currently having is trying to bind the click event to the button's context menu within the listboxitem. When the context menuitem is clicked, it says the action is not found.
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Button BorderBrush="Transparent" BorderThickness="0" telerik:StyleManager.Theme="Windows8" Click="ButtonBase_OnClick">
<StackPanel Name="Panel" SnapsToDevicePixels="True"
Orientation="Horizontal" Margin="1 0"
Height="30">
<ContentControl Margin="0 0 10 0" Height="20">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="ContentTemplate" Value="{StaticResource Icons.File}"></Setter>
</Style>
</ContentControl.Style>
</ContentControl>
<TextBlock Foreground="Black" Text="{Binding FileName}"
VerticalAlignment="Center"
TextAlignment="Center"
Margin="1 0 0 0"/>
<Button x:Name="ExpandButton" Background="Transparent" Click="ExpandButton_OnClick" BorderThickness="0" VerticalAlignment="Center" ContextMenuService.IsEnabled="false">
<Button.ContextMenu>
<ContextMenu x:Name="popup">
<MenuItem Header="Open" cal:Message.Attach="[Click] = [Open($this)]"></MenuItem>
</ContextMenu>
</Button.ContextMenu>
<ContentControl ContentTemplate="{StaticResource Icons.ArrowUp}" Width="10" Height="10" Margin="2" VerticalAlignment="Center"/>
</Button>
<Rectangle Width="2" Fill="Gray" Margin="0 0 0 0"/>
</StackPanel>
</Button>
</ControlTemplate>
I could bind it behind code(xaml.cs) side of the application but I also lose track of what item is the context suppose to point to. To do that, i replaced caliburn's click event with a regular Click event. The SelectedItem and SelectedItems is null or empty, respectively.
private void MenuItem_OnClick(object sender, RoutedEventArgs e)
{
var originalSource = e.OriginalSource;
var selectedItem = FileListBox.SelectedItem;
var SelectedItems = FileListBox.SelectedItems;
}
Haven't tested but something along these lines should open the context menu on right or left click:
<Button x:Name="ExpandButton" Background="Transparent" Click="ContextMenu_Click" BorderThickness="0" VerticalAlignment="Center" ContextMenuService.IsEnabled="false">
<Button.Style>
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<EventTrigger RoutedEvent="Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="ContextMenu.IsOpen">
<DiscreteBooleanKeyFrame KeyTime="0:0:0" Value="True"/>
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu x:Name="popup" MenuItem.Click="menuItem_Click">
<MenuItem Header="Open" cal:Message.Attach="[Click] = [Open($this)]"></MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
<ContentControl ContentTemplate="{StaticResource Icons.ArrowUp}" Width="10" Height="10" Margin="2" VerticalAlignment="Center"/>
</Button>
As for the code-behind, the following worked for me in my last tug with a similar issue:
DependencyObject mainDep = new DependencyObject();
private void ContextMenu_Click(object sender, RoutedEventArgs e)
{
DependencyObject dep = (DependencyObject)e.OriginalSource;
while ((dep != null) && !(dep is ListBoxItem))
{
dep = VisualTreeHelper.GetParent(dep);
}
mainDep = dep;
}
private void menuItem_Click(object sender, RoutedEventArgs e)
{
DependencyObject dep = mainDep;
if (dep is ListBoxItem)
{
...
DO your stuff here
...
}
}
Let me know how these work for you
I've been trying to display a message in a control which only appears for a few seconds when I recieve a message saying that an update which I have published has been successfully recieved.
I have a class which implements INotifyPropertyChanged as follows:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace Wpf.GUI.Views
{
public class AlertBarStatus : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event RoutedEventHandler VisibleChanged;
private bool _visible ;
private bool _success ;
public AlertBarStatus():this (false,false)
{
}
public AlertBarStatus(bool visible, bool success)
{
_visible = visible;
_success = success;
}
public bool Visible
{
get { return _visible; }
set
{
_visible = value;
OnPropertyChanged("Visible");
if(VisibleChanged != null)
{
VisibleChanged(this, null);
}
}
}
public bool Success
{
get { return _success; }
set
{
_success = value;
OnPropertyChanged("Success");
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I have xaml which contains the control which is as follows:
<UserControl x:Class="Wpf.GUI.AlertBarControl"
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"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Border x:Name="alertStatusBorder" Height="50" Visibility="Collapsed" Opacity="0" HorizontalAlignment="Stretch" >
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding AlertBarStatus.Success}" Value="True">
<Setter Property="Background" Value="{StaticResource EnabledBorderBackground}" />
</DataTrigger>
<DataTrigger Binding="{Binding AlertBarStatus.Success}" Value="False">
<Setter Property="Background" Value="{StaticResource DisabledBorderBackground}" />
</DataTrigger>
<DataTrigger x:Name="VisibilityTrigger" Binding="{Binding AlertBarStatus.Visible}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard x:Name="VisibilityStoryboard">
<ObjectAnimationUsingKeyFrames Duration="0:0:0" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="0" To="100" Duration="0:0:5" />
<DoubleAnimation Storyboard.TargetProperty="Opacity"
From="100" To="0" Duration="0:0:5" />
<ObjectAnimationUsingKeyFrames Duration="0:0:5" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="VisibilityStoryboard"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<DockPanel >
<TextBlock DockPanel.Dock="Left" x:Name="alertStatusText" Grid.Column="0" HorizontalAlignment="Left">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding AlertBarStatus.Success}" Value="True">
<Setter Property="Text" Value="Successfully published updates" />
</DataTrigger>
<DataTrigger Binding="{Binding AlertBarStatus.Success}" Value="False">
<Setter Property="Text" Value="Failed to publish updates" />
</DataTrigger>
</Style.Triggers>
<Setter Property="FontSize" Value="20"></Setter>
<Setter Property="VerticalAlignment" Value="Center"></Setter>
</Style>
</TextBlock.Style>
</TextBlock>
<!--<Button Height="20" Width="20" Margin="3" DockPanel.Dock="Right" HorizontalAlignment="Right"
Background="Transparent" BorderBrush="Black"
Click="CloseAlertControl_Click">X</Button>-->
</DockPanel>
</Border>
</Grid>
The DataTrigger named VisibilityTrigger does exactly what I want but ONLY when the Success value changes for the first time. It is not then called again.
I know there are several different answers to this question but I have tried all of the solutions I could find but so far none of them work for me.
To clarify, the visibility property can be set to true multiple times before it is set to false, therefore I was thinking that perhaps this could be the issue as I am not setting the value to a different value, but I can see the OnProperty changed method being called so I would not expect that to be an issue.
Any help on this would be much appreciated!
In the end I gave up on attempting to do this in xaml and instead just bound the objects visibility to my underlying object's visibility property as follows.
<Border x:Name="alertStatusBorder" Height="50" Visibility="{Binding Path=AlertBarStatus.Visible, Converter={StaticResource BoolToVis}}">
I then used a timer with an event which fired after 10 seconds which set the visibility variable to false and disabled the timer which worked like a charm.
Timer code:
Timer _alertBarTimer = new Timer(10000);
_alertBarTimer.Elapsed += _alert_timer_elapsed;
private void _alert_timer_elapsed(object sender, ElapsedEventArgs e)
{
alertBarControl.AlertBarStatus.Visible = false;
_alertBarTimer.Enabled = false;
}
Code which sets the visibility:
alertBarControl.AlertBarStatus.Visible = true;
_alertBarTimer.Enabled = true;
This is probably not the best way to do it but it certainly isn't the worst as I'm not directly setting the property of the control in code but instead I am using the object bound to the control.
If anyone does have a better way of doing this please feel free to contribute :)