WPF/XAML ProgressBar is not showing 100% width - wpf

I have a question about the XAML code of the ProgressBar. I have the following XAML code to display the Bar:
<ProgressBar Minimum="0" Maximum="100" Value="80" Style="{StaticResource Progress}" Grid.Row="1" Grid.Column="0" />
The control to display the bar is:
<!-- Progress bar control -->
<Style TargetType="ProgressBar" x:Key="Progress">
<Setter Property="Background" Value="{StaticResource colorProgressbackground}" />
<Setter Property="Foreground" Value="{StaticResource colorProgressactivity}" />
<Setter Property="BorderBrush" Value="{StaticResource colorProgressbackground}" />
<Setter Property="Height" Value="12" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ProgressBar}">
<Grid Name="TemplateRoot"
SnapsToDevicePixels="true">
<Rectangle Fill="{TemplateBinding Background}"
RadiusX="4"
RadiusY="4" />
<Border Background="{StaticResource colorProgressbackground}"
Margin="1"
CornerRadius="2" />
<Decorator Name="PART_Indicator" HorizontalAlignment="Left">
<Grid Name="Foreground">
<Grid Name="Animation" ClipToBounds="True">
<Border Name="PART_GlowRect" Width="{TemplateBinding Value}" BorderThickness="0" CornerRadius="4" Margin="0,0,0,0" HorizontalAlignment="Left" Background="{StaticResource colorProgressactivity}"/>
</Grid>
</Grid>
</Decorator>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Now, the problem is that the bar is 100% width, but the active bar (the Value-variable in the ProgressBar code) is not 80 percent, but 80 pixels (or so). I cannot give the Gray bar a hardcoded width. How can I make sure that the active bar (PART_indicator) is showing up correctly in percentages?
Thank you in advance!

Looking at the code for the control, it looks like it handles setting the 'Part_Indicator' width automatically.
From reflector
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this._track != null)
{
this._track.SizeChanged -= new SizeChangedEventHandler(this.OnTrackSizeChanged);
}
this._track = base.GetTemplateChild("PART_Track") as FrameworkElement;
this._indicator = base.GetTemplateChild("PART_Indicator") as FrameworkElement;
this._glow = base.GetTemplateChild("PART_GlowRect") as FrameworkElement;
if (this._track != null)
{
this._track.SizeChanged += new SizeChangedEventHandler(this.OnTrackSizeChanged);
}
if (this.IsIndeterminate)
{
this.SetProgressBarGlowElementBrush();
}
}
..
private void SetProgressBarIndicatorLength()
{
if ((this._track != null) && (this._indicator != null))
{
double minimum = base.Minimum;
double maximum = base.Maximum;
double num3 = base.Value;
double num4 = (this.IsIndeterminate || (maximum <= minimum)) ? 1.0 : ((num3 - minimum) / (maximum - minimum));
this._indicator.Width = num4 * this._track.ActualWidth;
this.UpdateAnimation();
}
}
This custom template doesn't seem to have to take the % into account, are you sure that the Part_Indicator is not correctly sizing but you are then sizing the Part_GlowRect incorrectly based on the Value ?

Related

WPF DocumentViewer enable scroll on touch screen for XPS document

When showing an XPS document in the DocumentViewer control of a WPF application it does not allow you to scroll its content on a touch enabled tablet just my moving your fingers over the screen.
Instead it selects the text. The only way of scrolling on a touch enabled device is by using the vertical scrollbar.
Is there a way to enable touch scrolling by moving your fingers on the content itself instead of on the vertical scrollbar?
By overriding some styles I could prevent the text selection but it still does not allow me to scroll. ( https://stackoverflow.com/a/415155/187650 )
I had the same problem and i made a solution and it works perfectly.
I made an own Xps Document class to set URI dynamically:
public class ManualXpsDocument : XpsDocument
{
private const string _uriOfDoc= "...\\path.xps";
public ManualXpsDocument(FileAccess fileAccess) : base(_uriOfDoc, fileAccess)
{
}
}
Here is the xaml part of window (or control):
<Grid.Resources>
<ObjectDataProvider x:Key="myDataSource" MethodName="GetFixedDocumentSequence"
ObjectType="{x:Type manual:ManualXpsDocument}">
<ObjectDataProvider.ConstructorParameters>
<io:FileAccess>Read</io:FileAccess>
</ObjectDataProvider.ConstructorParameters>
</ObjectDataProvider>
</Grid.Resources>
<DocumentViewer x:Name="Viewer" Document="{Binding Source={StaticResource myDataSource}}">
<DocumentViewer.Resources>
<Style TargetType="ToolBar">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="Visibility" Value="Collapsed" />
</Style>
</DocumentViewer.Resources>
<DocumentViewer.Style>
<Style TargetType="{x:Type DocumentViewer}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DocumentViewer}">
<controls:CustomScrollViewer x:Name="PART_ContentHost">
<controls:CustomScrollViewer.Style>
<Style TargetType="{x:Type ScrollViewer}">
<Setter Property="Focusable" Value="false" />
<Setter Property="IsDeferredScrollingEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ScrollViewer}">
<Border BorderBrush="#00000000"
BorderThickness="0,2,0,0">
<Grid Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollContentPresenter Name="PART_ScrollContentPresenter"
ScrollViewer.IsDeferredScrollingEnabled="True"
KeyboardNavigation.DirectionalNavigation="Local"
CanContentScroll="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<controls:CustomScrollBar Name="PART_VerticalScrollBar"
Style="{DynamicResource ScrollBar}"
Grid.Column="1"
Value="{TemplateBinding VerticalOffset}"
Maximum="{TemplateBinding ScrollableHeight}"
ViewportSize="{TemplateBinding ViewportHeight}"
Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}" />
<controls:CustomScrollBar Name="PART_HorizontalScrollBar"
Grid.Row="1"
Grid.ColumnSpan="2"
Style="{DynamicResource ScrollBar}"
Orientation="Horizontal"
Value="{TemplateBinding HorizontalOffset}"
Maximum="{TemplateBinding ScrollableWidth}"
ViewportSize="{TemplateBinding ViewportWidth}"
Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</controls:CustomScrollViewer.Style>
</controls:CustomScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DocumentViewer.Style>
</DocumentViewer>
Here is The xaml.cs part of Window (or control):
public ctor()
{
InitializeComponent();
PreviewTouchMove += ScrollingHandler;
}
private void ScrollingHandler(object sender, TouchEventArgs e)
{
TouchPoint tp = e.GetTouchPoint(Viewer);
if (tp.Action == TouchAction.Move)
{
//_scrollingInt is not necessary but Move procedure of Viewer has const value to scroll and the optimalization is recommended.
if (_lastYPosition > tp.Position.Y + _scrollingInt)
{
Viewer.MoveDown();
_lastYPosition = tp.Position.Y;
}
else if (_lastYPosition < tp.Position.Y - _scrollingInt)
{
Viewer.MoveUp();
_lastYPosition = tp.Position.Y;
}
}
// Viewer.IsHitTestVisible = false; this setting can disable the selection too,but if you use this, the hyperlinks won't work too.
Viewer.GetType().GetProperty("IsSelectionEnabled", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(Viewer, false, null);
//set false "IsSelectionEnabled" "hidden" property instead of Viewer.IsHitTestVisible = false, because the hyperlinks will work too.
e.Handled = true;
}
ScrollingHandler method solve the problem. This procedure do the scrolling of document viewer and disable the selection but the hyperlink function still available.

IsMouseOver trigger doesn't work when using ShowDialog and borderless window

I have two Windows for an application. One of them is MainWindow and the other is for settings. SettingsWindow opens when settings button is clicked by using ShowDialog and setting its Owner to MainWindow.
On the SettingsWindow I have a button at the very bottom of the window and it changes the color to red when IsMouseOver is True and blue for False. But it doesn't change when the cursor is over the MainWindow. The image is below to be clear. How can I fix this problem?
CASE: The cursor is out of SettingsWindow but it keeps the red color, no change.
Xaml code:
<Window x:Class="AltoSS.SettingsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SettingsWindow"
Height="150"
Width="360"
WindowStyle="None"
AllowsTransparency="True"
WindowStartupLocation="CenterOwner">
<!-- Other control codes-->
<Button Grid.Row="2" Content="KAYDET"
FontSize="15"
FontWeight="Bold"
BorderBrush="Gray"
BorderThickness="0,2,0,2">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="Foreground" Value="White"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
</Window>
Alright, after doing some research, I couldn't find any logical reason for this to occur. It seems more like a bug to me. So if anyone knows exactly why this happens, let us know!
Anyway, I've come up with a workaround. Basically, we can use Show() and add some code to get closer to a modal behavior - like disabling the parent window until the dialog gets closed or the user has selected OK or Cancel for instance.
Example:
SettingsWindow settingsWindow = new SettingsWindow();
this.IsEnabled = false; //disables the main window
settingsWindow.Owner = this; // main window is the settings window owner
settingsWindow.Show();
settingsWindow.Closed += (o, e1) => { onWindowClosed(o,e1); }; // this is the close event
After subscribing for the settingsWindow closed event, we can now enable the parent window again when settingsWindow gets closed:
private void onWindowClosed(object sender, EventArgs e)
{
this.IsEnabled = true;
}
Triggers will now work correctly and parent window gets disabled until its child is closed.
I think you have to observe the mouse position manually. For this you could use the code behind posted by Peheje here.
I used this to program a working example. While leaving your window, the Button gets the correct style.
using System.Runtime.InteropServices;
using Point = System.Drawing.Point;
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(ref Point lpPoint);
public bool IsMouseOverButton {
get { return _isMouseOverButton; }
set {
if (value == _isMouseOverButton) return;
_isMouseOverButton = value;
OnPropertyChanged();
}
}
public SettingsWindow()
{
InitializeComponent();
new Thread(() =>
{
while (true)
{
//Logic
Point p = new Point();
GetCursorPos(ref p);
//Update UI
Application.Current.Dispatcher.Invoke(() =>
{
double btnLeft = DlgWindow.Left;
double btnRight = btnLeft + DlgBtn.ActualWidth;
double btnBottom = DlgWindow.Top + DlgWindow.ActualHeight;
double btnTop = btnBottom - DlgBtn.ActualHeight;
IsMouseOverButton =
p.X >= btnLeft && p.X <= btnRight &&
p.Y >= btnTop && p.Y <= btnBottom;
});
//async wait (non blocking)
(new ManualResetEvent(false)).WaitOne(100);
}
}).Start();
}
xaml
<Window x:Name="DlgWindow"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
AllowsTransparency="True">
<Button x:Name="DlgBtn"
Height="50"
VerticalAlignment="Bottom"
BorderBrush="Gray"
BorderThickness="0,2,0,2"
Content="KAYDET"
FontSize="15"
FontWeight="Bold">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Background" Value="Blue"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding IsMouseOverButton}" Value="True">
<Setter Property="Background" Value="Red" />
<Setter Property="Foreground" Value="White" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Button.Style>
</Button>
This works for me: Define a style resource (for the button) under settings Window.Resources - This style then sets the new Template (a border), the default background as blue, and the IsMouseOver trigger to change it to red. Reference the style either explicitly for implicitly (both worked for me).
Link to small test project: https://1drv.ms/u/s!AhlMAmchX3R6nDJ1MXS6DxlRXtnA
<Window x:Class="IsMouseOverTriggerSecondWindow.SettingsWindow"
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:IsMouseOverTriggerSecondWindow"
mc:Ignorable="d"
Title="SettingsWindow" Height="170" Width="330">
<!-- my settings window button style-->
<!-- defined as a resource in SettingsWindow.xaml, so it doesnt effect MainWindow -->
<Window.Resources>
<Style TargetType="Button" >
<Setter Property="Background" Value="Blue"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBlock Text="This is the settings window" />
<Button Content="KAYDET" Height="30" VerticalAlignment="Bottom" Foreground="White" FontWeight="Bold" />
</Grid>
</Window>

WindowChrome with AllowsTransparency = true and WindowStyle = None issues

I have an application that i override the main shell style with windowchrome, as follow:
XAML:
<Style TargetType="{x:Type local:MainLayoutWindow}">
<Setter Property="shell:WindowChrome.WindowChrome">
<Setter.Value>
<shell:WindowChrome
GlassFrameThickness="0"
ResizeBorderThickness="7"
CaptionHeight="36"
CornerRadius="3"
/>
</Setter.Value>
</Setter>
<Setter Property="WindowState" Value="Maximized" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MainLayoutWindow}">
<Border>
<Border.Style>
<Style TargetType="{x:Type Border}">
<Setter Property="BorderThickness" Value="{Binding WindowResizeBorderThickness}"/>
</Style>
</Border.Style>
<Grid x:Name="Root" >
<Grid>
<!-- Content Border -->
<!-- Header -->
</Grid>
<!-- Buttons -->
<Border BorderBrush="{StaticResource WindowButtonOutline}" BorderThickness="1,0,1,1" CornerRadius="0,0,3,3" VerticalAlignment="Top" Margin="7,1,0,0" HorizontalAlignment="Left">
<StackPanel Orientation="Horizontal">
<Button x:Name="PART_btnClose" Height="17" Width="35" IsTabStop="False" shell:WindowChrome.IsHitTestVisibleInChrome="True" >
</Button>
<Button x:Name="PART_btnMaxAndNormal" Height="17" Width="24" IsTabStop="False" shell:WindowChrome.IsHitTestVisibleInChrome="True" >
</Button>
<Button x:Name="PART_btnMinimize" Height="17" Width="24" IsTabStop="False" shell:WindowChrome.IsHitTestVisibleInChrome="True" >
</Button>
</StackPanel>
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="WindowState" Value="Maximized">
<Setter Property="shell:WindowChrome.WindowChrome">
<Setter.Value>
<shell:WindowChrome ResizeBorderThickness="0"/>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="WindowState" Value="Normal">
<Setter Property="shell:WindowChrome.WindowChrome">
<Setter.Value>
<shell:WindowChrome ResizeBorderThickness="7"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
CS:
public MyWindow()
{
this.AllowsTransparency = true;
this.WindowStyle = System.Windows.WindowStyle.None;
this.Caption = "some text";
this.StateChanged += MainLayoutWindow_StateChanged;
}
void MainLayoutWindow_StateChanged(object sender, EventArgs e)
{
if (this._myVM== null) return;
this._myVM.SetState(this.WindowState);
}
public MyWindow(MyVM myVM)
: this()
{
this.DataContext = mainVM;
this._myVM= myVM;
}
Thickness _maximizeThickness = new Thickness(8, 8, 8, 8);
Thickness _normalThickness = new Thickness(0, 0, 0, 0);
Thickness _windowResizeBorderThickness = new Thickness(0, 0, 0, 0);
const string WINDOWRESIZEBORDERTICKNESS = "WindowResizeBorderThickness";
public Thickness WindowResizeBorderThickness
{
get
{
return _windowResizeBorderThickness;
}
set
{
if (_windowResizeBorderThickness != value)
{
_windowResizeBorderThickness = value;
OnPropertyChanged(WINDOWRESIZEBORDERTICKNESS);
}
}
}
public void SetState(WindowState windowState)
{
if (windowState == WindowState.Maximized)
{
WindowResizeBorderThickness = _maximizeThickness;
}
else
{
WindowResizeBorderThickness = _normalThickness;
}
}
Issues:
If I don't using AllowsTransparency = true and WindowStyle=None, it seems that sometimes is see the original buttons before the style is loaded. Or if have any action against the server that takes long time.
If I set AllowsTransparency = true and WindowStyle=None, the app is taken all width and height of screen (the taskbar is not showing).
For multiple screens, when the app is open in maximize mode on the primary screen and I moved to secondary screen with maximize mode id does it but I cannot do it from secondary screen to primary screen, it not allow to move to maximize mode.
Any ideas? thanks

How To Find ScrollViewer Inside of WPF ContentControl?

WPF 4.5 / C#
I've got an app where I have several WPF Windows each utilizing this custom content control. I use it in the XAML like this:
<ContentControl Name="myControl" Style="{StaticResource ReservedSpaceScrollBar}"
In the code behind, I need to be able to access the ScrollViewer inside, so I can call .ScrollToTop()
I've tried this, but it doesn't work:
((ScrollViewer)this.myControl.FindName("Scroll")).ScrollToTop();
...but .FindName doesn't find the ScrollViewer. What am I doing wrong? How make this work?
The XAML for the Style is below...
<Style TargetType="{x:Type ContentControl}" x:Key="ReservedSpaceScrollBar">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<ScrollViewer PanningMode="Both" VerticalScrollBarVisibility="Auto" x:Name="Scroll" FocusVisualStyle="{x:Null}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter />
<Border Width="{x:Static SystemParameters.VerticalScrollBarWidth}" x:Name="Placeholder" Grid.Column="1" />
</Grid>
</ScrollViewer>
<ControlTemplate.Triggers>
<DataTrigger Binding="{Binding ComputedVerticalScrollBarVisibility, ElementName=Scroll}" Value="Visible">
<Setter TargetName="Placeholder" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Look into the VisualTreeHelper
Using that class you can look into children of elements; for example:
var childCount = VisualTreeHelper.GetChildrenCount(this.myControl);
for (int i = 0; i < childCount; i++)
{
var child = VisualTreeHelper.GetChild(this.myControl, i);
if (child.GetValue(NameProperty).ToString() == "Scroll")
{
((ScrollViewer)child).ScrollToTop();
}
}

How to display a different value for dropdown list values/selected item in a WPF ComboBox?

I have a WPF combobox bound to a list of items with long descriptions.
The type bound to the ComboBox has both short and long description as properties. Currently, I am binding to the full description.
comboBox.DisplayMemberPath = "FullDescription";
How to ensure that when the item is selected and displayed as a single item in the combobox, it will be displayed as a value of the ShortDescription property while the dropdown will display FullDescription?
Update 2011-11-14
I recently came upon the same requirement again and I wasn't very happy with the solution I posted below. Here is a nicer way to get the same behavior without re-templating the ComboBoxItem. It uses a DataTemplateSelector
First, specify the regular DataTemplate, the dropdown DataTemplate and the ComboBoxItemTemplateSelector in the resources for the ComboBox. Then reference the ComboBoxItemTemplateSelector as a DynamicResource for ItemTemplateSelector
<ComboBox ...
ItemTemplateSelector="{DynamicResource itemTemplateSelector}">
<ComboBox.Resources>
<DataTemplate x:Key="selectedTemplate">
<TextBlock Text="{Binding Path=ShortDescription}"/>
</DataTemplate>
<DataTemplate x:Key="dropDownTemplate">
<TextBlock Text="{Binding Path=FullDescription}"/>
</DataTemplate>
<local:ComboBoxItemTemplateSelector
x:Key="itemTemplateSelector"
SelectedTemplate="{StaticResource selectedTemplate}"
DropDownTemplate="{StaticResource dropDownTemplate}"/>
</ComboBox.Resources>
</ComboBox>
ComboBoxItemTemplateSelector checks if the container is the child of a ComboBoxItem, if it is, then we are dealing with a dropdown item, otherwise it is the item in the ComboBox.
public class ComboBoxItemTemplateSelector : DataTemplateSelector
{
public DataTemplate DropDownTemplate
{
get;
set;
}
public DataTemplate SelectedTemplate
{
get;
set;
}
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
ComboBoxItem comboBoxItem = VisualTreeHelpers.GetVisualParent<ComboBoxItem>(container);
if (comboBoxItem != null)
{
return DropDownTemplate;
}
return SelectedTemplate;
}
}
GetVisualParent
public static T GetVisualParent<T>(object childObject) where T : Visual
{
DependencyObject child = childObject as DependencyObject;
while ((child != null) && !(child is T))
{
child = VisualTreeHelper.GetParent(child);
}
return child as T;
}
Old solution, requires re-templating of ComboBoxItem
<SolidColorBrush x:Key="SelectedBackgroundBrush" Color="#DDD" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
<ControlTemplate x:Key="FullDescriptionTemplate" TargetType="ComboBoxItem">
<Border Name="Border" Padding="2" SnapsToDevicePixels="true">
<StackPanel>
<TextBlock Text="{Binding Path=FullDescription}"/>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="true">
<Setter TargetName="Border" Property="Background" Value="{StaticResource SelectedBackgroundBrush}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<ComboBox Name="c_comboBox" ItemsSource="{Binding}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ShortDescription}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemContainerStyle>
<Style TargetType="{x:Type ComboBoxItem}">
<Setter Property="Template" Value="{StaticResource FullDescriptionTemplate}" />
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
This results in the following behavior
It doesn't seem to work for me now, but this one does:
public class ComboBoxItemTemplateSelector : DataTemplateSelector {
public DataTemplate SelectedTemplate { get; set; }
public DataTemplate DropDownTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
var presenter = (ContentPresenter)container;
return (presenter.TemplatedParent is ComboBox) ? SelectedTemplate : DropDownTemplate;
}
}
I modified this custom rounded WPF ComboBox to display a different value from the item selected as well as change the color for each item.
Custom ComboBox
First you need to create the structure:
//Structure
public class COMBOITEM
{
string _ITEM_NAME;
string _ITEM_SHORT_NAME;
Brush _ITEM_COLOR;
public string ITEM_NAME
{
get { return _ITEM_NAME; }
set { _ITEM_NAME = value; }
}
public string ITEM_SHORT_NAME
{
get { return _ITEM_SHORT_NAME; }
set { _ITEM_SHORT_NAME = value; }
}
public Brush ITEM_COLOR
{
get { return _ITEM_COLOR; }
set { _ITEM_COLOR = value; }
}
}
Initialize the structure, fill it with data and bind to ComboBox:
private void Load_Data()
{
Brush Normal_Blue = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#FF1F4E79"));
//Load first entry
ObservableCollection<COMBOITEM> _Line_Data = new ObservableCollection<COMBOITEM>();
_Line_Data.Add(new COMBOITEM() { ITEM_NAME = "Line Number 1", ITEM_SHORT_NAME = "LN 1", ITEM_COLOR = Normal_Blue });
//Load Test Data
for (int i = 2; i < 10; i++)
{
_Line_Data.Add(new COMBOITEM()
{
ITEM_NAME = "Line Number " + i.ToString(),
ITEM_SHORT_NAME = "LN " + i.ToString(),
ITEM_COLOR = (i % 2 == 0) ? new SolidColorBrush(Colors.Green) : new SolidColorBrush(Colors.Red) //This just changes color
});
}
//Bind data to combobox
cb_Test.ItemsSource = _Line_Data;
}
Now place the ComboBox in your design. To use it as a normal ComboBox, remove DisplayMemberPath and rename "ColorComboBoxItem" to "CustomComboBoxItem":
<ComboBox x:Name="cb_Test" FontSize="36" Padding="1,0" MinWidth="100" MaxWidth="400" Margin="5,53,10,207" FontFamily="Calibri" Background="#FFBFBFBF" Foreground="#FF1F4E79" BorderBrush="#FF1F4E79" VerticalContentAlignment="Center" TabIndex="5" IsSynchronizedWithCurrentItem="False"
Style="{DynamicResource RoundedComboBox}"
ItemContainerStyle="{DynamicResource ColorComboBoxItem}"
DisplayMemberPath="ITEM_SHORT_NAME" />
Now add the following styles/template to App.xaml Application.Resources:
<!-- Rounded ComboBox Button -->
<Style x:Key="ComboBoxToggleButton" TargetType="ToggleButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="32" />
</Grid.ColumnDefinitions>
<Border
x:Name="Border"
Grid.ColumnSpan="2"
CornerRadius="8"
Background="{TemplateBinding Background}"
BorderBrush="#FF1F4E79"
BorderThickness="2"
/>
<Path
x:Name="Arrow"
Grid.Column="1"
Fill="{TemplateBinding Foreground}"
Stroke="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 0 L 4 4 L 8 0 Z"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="ComboBoxTextBox" TargetType="TextBox">
<Border x:Name="PART_ContentHost" Focusable="True" />
</ControlTemplate>
<!-- ComboBox Template -->
<Style x:Key="RoundedComboBox" TargetType="{x:Type ComboBox}">
<Setter Property="Foreground" Value="#333" />
<Setter Property="BorderBrush" Value="Gray" />
<Setter Property="Background" Value="White" />
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="FontSize" Value="13" />
<Setter Property="MinWidth" Value="150"/>
<Setter Property="MinHeight" Value="35"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBox">
<Grid>
<ToggleButton
Cursor="Hand"
Name="ToggleButton"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Style="{StaticResource ComboBoxToggleButton}"
Grid.Column="2"
Focusable="false"
IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<ContentPresenter
Name="ContentSite"
IsHitTestVisible="False"
Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}"
Margin="10,3,30,3"
VerticalAlignment="Center"
HorizontalAlignment="Left" />
<TextBox x:Name="PART_EditableTextBox"
Style="{x:Null}"
Template="{StaticResource ComboBoxTextBox}"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="3,3,23,3"
Focusable="True"
Visibility="Hidden"
IsReadOnly="{TemplateBinding IsReadOnly}"/>
<Popup
Name="Popup"
Placement="Bottom"
IsOpen="{TemplateBinding IsDropDownOpen}"
AllowsTransparency="True"
Focusable="False"
PopupAnimation="Slide">
<Grid
Name="DropDown"
SnapsToDevicePixels="True"
MinWidth="{TemplateBinding ActualWidth}"
MaxHeight="{TemplateBinding MaxDropDownHeight}">
<Border
CornerRadius="10"
x:Name="DropDownBorder"
Background="#FFBFBFBF"
BorderThickness="2"
BorderBrush="#FF1F4E79"
/>
<ScrollViewer Margin="4,6,4,6" SnapsToDevicePixels="True">
<StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
</ScrollViewer>
</Grid>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasItems" Value="false">
<Setter TargetName="DropDownBorder" Property="MinHeight" Value="95"/>
</Trigger>
<Trigger Property="IsGrouping" Value="true">
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</Trigger>
<Trigger Property="IsEditable" Value="true">
<Setter Property="IsTabStop" Value="false"/>
<Setter TargetName="PART_EditableTextBox" Property="Visibility" Value="Visible"/>
<Setter TargetName="ContentSite" Property="Visibility" Value="Hidden"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
</Style.Triggers>
</Style>
<!--This style uses the normal items.add function-->
<Style x:Key="CustomComboBoxItem" TargetType="{x:Type ComboBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="30" />
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border
Name="Border"
Padding="5"
Margin="2"
BorderThickness="2,0,0,0"
CornerRadius="0"
Background="Transparent"
BorderBrush="Transparent">
<TextBlock TextAlignment="Left">
<ContentPresenter />
</TextBlock>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="true">
<Setter TargetName="Border" Property="BorderBrush" Value="#FF3737CB"/>
<Setter TargetName="Border" Property="Background" Value="#FF6ACDEA"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--This style uses the structure to fill items and set the item color-->
<Style x:Key="ColorComboBoxItem" TargetType="{x:Type ComboBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="30" />
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Foreground" Value="{Binding ITEM_COLOR}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border
Name="Border"
Padding="5"
Margin="2"
BorderThickness="2,0,0,0"
CornerRadius="0"
Background="Transparent"
BorderBrush="Transparent">
<TextBlock Text="{Binding ITEM_NAME}" TextAlignment="Left">
</TextBlock>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsHighlighted" Value="true">
<Setter TargetName="Border" Property="BorderBrush" Value="#FF3737CB"/>
<Setter TargetName="Border" Property="Background" Value="#FF6ACDEA"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I hope this helps..
This solution is for WPF + MVVM.
Some of the other solutions work, and some of them do not. The problem with some other solutions are that if they do not work, it's sometimes difficult to debug why it is not working, especially if one is not experienced with WPF.
In my opinion, it's preferable to use strings for the bindings, and convert to an enum in C# which means everything is easier to troubleshoot.
You might need to use ReSharper, it will auto-suggest any missing namespaces.
Create an enum with description attributes:
public enum EnumSelectedView
{
[Description("Drop Down 1")]
DropDown1 = 0,
[Description("Drop Down 2")]
DropDown2 = 1,
}
And a ComboBox:
<ComboBox HorizontalAlignment="Right"
VerticalAlignment="Top"
Width="130"
ItemsSource="{Binding AvailableSelectedViews, Mode=OneWay}"
SelectedItem="{Binding SelectedView, Mode=TwoWay, Converter={StaticResource enumToDescriptionConverter}}"
</ComboBox>
The converter in XAML needs to be pointed at the C# class. If you are using a UserControl or a Window, it would be UserControl.Resources or Window.Resources.
<DataTemplate.Resources>
<converters:EnumToDescriptionConverter x:Key="enumToDescriptionConverter" />
</DataTemplate.Resources>
Add some extension methods and a converter anywhere in your project:
using System;
namespace CMCMarkets.Phantom.CoreUI.Converters
{
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows.Data;
public class EnumToDescriptionConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value is Enum) == false) throw new ArgumentException("Error: value is not an enum.");
return ((Enum)value)?.GetDescriptionAttribute();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if ((value is string) == false)
{
throw new ArgumentException("Error: Value is not a string");
}
foreach (var item in Enum.GetValues(targetType))
{
var asString = (item as Enum).GetDescriptionAttribute();
if (asString == (string)value)
{
return item;
}
}
throw new ArgumentException("Error: Unable to match string to enum description.");
}
}
public static class EnumExtensions
{
/// <summary>
/// For a single enum entry, return the [Description("")] attribute.
/// </summary>
public static string GetDescriptionAttribute(this Enum enumObj)
{
FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());
object[] attribArray = fieldInfo.GetCustomAttributes(false);
if (attribArray.Length == 0)
{
return enumObj.ToString();
}
else
{
DescriptionAttribute attrib = attribArray[0] as DescriptionAttribute;
return attrib?.Description;
}
}
/// <summary>
/// For an enum type, return a list of all possible [Description("")] attributes.
/// </summary>
/*
* Example: List<string> descriptions = EnumExtensions.GetDescriptionAttributeList<MyEnumType>();
*/
public static List<string> GetDescriptionAttributeList<T>()
{
return typeof(T).GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
}
/// <summary>
/// For an enum instance, return a list of all possible [Description("")] attributes.
/// </summary>
/*
* Example:
*
* List<string> descriptions = typeof(CryptoExchangePricingOrGraphView).GetDescriptionAttributeList();
*/
public static List<string> GetDescriptionAttributeList(this Type type)
{
return type.GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
}
/// <summary>
/// For an enum instance, return a list of all possible [Description("")] attributes.
/// </summary>
/*
* Example:
*
* MyEnumType x;
* List<string> descriptions = x.GetDescriptionAttributeList();
*/
public static List<string> GetDescriptionAttributeList(this Enum thisEnum)
{
return thisEnum.GetType().GetEnumValues().Cast<Enum>().Select(x => x.GetDescriptionAttribute()).ToList();
}
}
}
In your ViewModel:
public IReadOnlyList<string> AvailableSelectedViews { get; }
And in the constructor:
this.AvailableSelectedViews = typeof(EnumSelectedView).GetDescriptionAttributeList();
The selected item will be bound to this. It uses the converter to go from the string in the combobox straight to the enum. You could also do the conversion inside the property updater by using the extension methods above.
public EnumSelectedView SelectedView { get; set; }
Another option I have found is to place a textbox over the combobox text area. Size and align it so that it lays perfectly over it then use a sub similar to this:
Private Sub ComboBox*_Change()
Dim T As String
T = Left(ComboBox*.Text, 1)
TextBox*.Value = T
End Sub
(replace the * with the relevant numbers)
the result is that when selected the dropdown will display the list as usual but the textbox lying over it will only show its first character.
Hope this helps.
The accepted solution only works if IsEditable is false.
If IsEditable is true, i.e., if the control is a "real" combo box in the sense of combining a list and a free-input text box, there is a really simple solution:
<ComboBox ...
DisplayMemberPath="PropertyToUseForList"
TextSearch.TextPath="PropertyToUseForTextBox" />
Note that this works even if IsTextSearchEnable is false.

Resources