WPF ComboBox truncating items - c#

I found a strange behavior when using ComboBox with enum items. I noticed that the popup that displays the entries when I click on the ComboBox truncates long items. I figured out that this happens because I define a TextBlock style with a fixed Width. What is strange is that, the Width only affects the ComboBox when I use enum items. It does not happen If I use string ones instead.
Here's a picture with what's going on. The third item should be "VeryLongTypeName".
Here is the code sample written according with the MVVM pattern.
The UserControl XAML:
<UserControl.DataContext>
<local:SampleViewModel/>
</UserControl.DataContext>
<StackPanel>
<StackPanel.Resources>
<Style TargetType="TextBlock">
<Setter Property="Width" Value="70"/>
<Setter Property="Margin" Value="0,0,5,0"/>
</Style>
</StackPanel.Resources>
<DockPanel>
<TextBlock Text="Items"/>
<ComboBox ItemsSource="{Binding ItemsList}" SelectedItem="{Binding Item}"/>
</DockPanel>
<DockPanel>
<TextBlock Text="String Items"/>
<ComboBox ItemsSource="{Binding StringItemsList}" SelectedItem="{Binding StringItem}"/>
</DockPanel>
</StackPanel>
The SampleViewModel code:
public class SampleViewModel
{
public enum SomeType { Type1, Type2, VeryLongTypeName };
public IEnumerable<SomeType> ItemsList
{
get { return (SomeType[])Enum.GetValues(typeof(SomeType)); }
}
public SomeType Item { get { return ItemsList.First(); } set { } }
public IEnumerable<string> StringItemsList
{
get { return ItemsList.Select(type => type.ToString()); }
}
public string StringItem { get { return StringItemsList.First(); } set { } }
}
If you build the code sample, in the second ComboBox below the one from the picture, things go smoothly with string values.
I have the following questions:
Why does changing the type affect the graphics?
How do I fix the ComboBox display when using enum?
Any help is welcomed.

Your textblock style is for all textblocks. The content of the combobox is also displayed with textblocks and you limited the width of textblocks to 70.
Use a key for your style or set another textblock style for the comboboxes.

The problem also happens when listing items with a ListBox. I used Live Property Explorer to see what's going on. Both cases render the content in a TextBlock, but only when using enum values the style defined as resource is applied. Don't know why this happens, but that's how it is.
To fix the problem for enum and possibly other types except string, I added the following style, based on #Mardukar's idea:
<Style TargetType="ComboBoxItem">
<Style.Resources>
<Style TargetType="TextBlock" BasedOn="{x:Null}"/>
</Style.Resources>
</Style>
#Fredrik's idea of changing the ComboBox.ItemTemplate also works.
For ListBox, the style needs to have TargetType either ListBoxItem or ListBox.

Related

Is there a way to inject an Item/DataTemplate into a custom Style or UserControl?

Read update for a solution
I have a small WPF Application with several small game clones such as Minesweeper, Connect 4, Tic Tac Toe among others.
Common for all of these is that they are all a uniform grid of squares, each square is controlled by a button.
For each of these games I have defined a UserControl with a UniformGrid ItemsPanelTemplate in their XAML.
The only place they differ is the DataTemplate used:
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- Button that fit the specific need of the game -->
</DataTemplate>
</ItemsControl.ItemTemplate>
To avoid repetition I wanted to create a UserControl which has a DataTemplate dependency property (and has an ItemsControl defined in the XAML named itemscontrol):
public DataTemplate DataTemplate
{
get => (DataTemplate)GetValue(DataTemplateProperty);
set => SetValue(DataTemplateProperty, value);
}
public static readonly DependencyProperty DataTemplateProperty =
DependencyProperty.Register("DataTemplate", typeof(DataTemplate), typeof(BoardGameControl));
public BoardGameControl()
{
InitializeComponent();
itemscontrol.ItemTemplate = DataTemplate;
}
Which I tried to use in my application like so:
<controls:BoardGameControl>
<controls:BoardGameControl.DataTemplate>
<DataTemplate>
<Button Content="Hi"/>
</DataTemplate>
</controls:BoardGameControl.DataTemplate>
</controls:BoardGameControl>
I have tried some other approaches as well but none have worked.
How can I avoid having to define a new ItemsControl for each game and instead have a UserControl or Style that simply accepts a different Button depending on the situation?
Update
I combined both the solution I marked as Accepted and the comment by #Joe on this post.
Instead of a UserControl I created a Custom Control with my desired properties and then styled in in the Generic.xaml file to my liking. I also removed the DataTemplate property from my Custom Control and instead added DataTemplates in my App.xaml for each different VM.
Below you will find the code behind and style of my new Custom Control.
public class GameBoardControl : Control
{
public int Columns
{
get => (int)GetValue(ColumnsProperty);
set => SetValue(ColumnsProperty, value);
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(nameof(Columns), typeof(int), typeof(BoardGameControl));
public int Rows
{
get => (int)GetValue(RowsProperty);
set => SetValue(RowsProperty, value);
}
public static readonly DependencyProperty RowsProperty =
DependencyProperty.Register(nameof(Rows), typeof(int), typeof(BoardGameControl));
static GameBoardControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(GameBoardControl), new FrameworkPropertyMetadata(typeof(GameBoardControl)));
}
}
<Style TargetType="{x:Type controls:GameBoardControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:GameBoardControl}">
<ItemsControl ItemsSource="{Binding Squares}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="{Binding Columns, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GameBoardControl}}}"
Rows="{Binding Rows, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:GameBoardControl}}}"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note that I always bind the ItemsSource to Squares, I am able to do this since all of my games have an ObservableCollection called Squares where the Square View Models are stored.
Example of a DataTemplate
<DataTemplate DataType="{x:Type mvvmtoolkit:MemorySquareVM}">
<Button Command="{Binding RelativeSource={RelativeSource AncestorType=controls:GameBoardControl}, Path=DataContext.PressSquareCommand}"
CommandParameter="{Binding}"
Background="{Binding Position, Converter={local:PositionToColorConverter}}"
Style="{StaticResource MemoryButton}"/>
</DataTemplate>
If I understand correctly what you need, then you need to slightly change the implementation: You should not use value assignment in Code Behind - Instead, you need to use binding in XAML.
public BoardGameControl()
{
InitializeComponent();
// itemscontrol.ItemTemplate = DataTemplate;
}
<ItemsControl ItemTemplate="{Binding DataTemplate, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:BoardGameControl}}}">
I also strongly advise in such cases (when properties are added to an element, its behavior is changed, etc.) to use not UserControl, but Custom Control.
why my implementation did not work?
Implementation on Sharp:
public static readonly DependencyProperty DataTemplateProperty =
DependencyProperty.Register("DataTemplate", typeof(DataTemplate), typeof(BoardGameControl),
new PropertyMetadata((d, e) => ((BoardGameControl)d).itemscontrol.ItemTemplate = (IEnumerable) e.NewValue));
public BoardGameControl()
{
InitializeComponent();
// itemscontrol.ItemTemplate = DataTemplate;
}
I would make a Custom Control, however I do not know how to implement the logic of an ItemsControl / UniformGrid myself
If you work in the Studio, then select "Add" -> "Create element" in the context menu. A combo box will appear and select "Custom Control WPF" from it.
You will have a class code derived from Control. Add properties and the logic of behavior you need to it.
And the "Themes/Generic.xaml" file will also be created - this is the default theme. This theme will have a template for your element - edit it as you need.

WPF Tree in ComboEditor - Infragistics WPF controls

I'm currently working on a WPF .NET 4.7 application and I use Infragistics WPF controls version 18.
I need to create a custom XamComboEditor which has a XamDataTree inside. Thus a ComboBox with a Tree selection inside.
The Tree selection works fine without the XamComboEditor and looks like this:
<iWPF:XamDataTree ItemsSource="{Binding Locations}">
<iWPF:XamDataTree.GlobalNodeLayouts>
<iWPF:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="Name"/>
<iWPF:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iWPF:XamDataTree.GlobalNodeLayouts>
</iWPF:XamDataTree>
My XamDataTree is bound to an observable collection Locations:
public ObservableCollection<LocationViewModel> Locations { get; set; } = new ObservableCollection<LocationViewModel>();
public class LocationViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<LocationViewModel> ChildLocations { get; set; } = new List<LocationViewModel>();
}
I need to use the style setter on my XamComboEditor to put the XamDataTree inside the combobox.
My problem is now, I don't know how to achieve this, or how to pass the context from the XamComboEditor further to the XamDataTree.
I tried the following, in vain:
<iWPF:XamComboEditor ItemsSource="{Binding Locations}">
<iWPF:XamComboEditor.ComboBoxStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<iXaml:XamDataTree ItemsSource="{Binding .}">
<iXaml:XamDataTree.GlobalNodeLayouts>
<iXaml:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="{Binding Name}"/>
<iXaml:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iXaml:XamDataTree.GlobalNodeLayouts>
</iXaml:XamDataTree>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</iWPF:XamComboEditor.ComboBoxStyle>
</iWPF:XamComboEditor>
Do you know how to solve this issue? Do you know how to pass the data context from the parent control to, let's say, the child control? Or rather, how to put the XamDataTree inside the XamComboEditor?
If I understood this correctly, the DataContext of your XamlDataTree is no what you expect it to be (the Locations bound in you XamComboEditor).
One way to solve this problem is to specify the source of the path in your Binding markup extension.
You can use the {x:Reference ...} markup extension to reference a named controled in your control tree.
<iWPF:XamComboEditor x:Name="comboEditor" ItemsSource="{Binding Locations}">
<iWPF:XamComboEditor.ComboBoxStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<iXaml:XamDataTree ItemsSource="{Binding Source={x:Reference Name=comboEditor}, Path=DataContext.Locations}">
<iXaml:XamDataTree.GlobalNodeLayouts>
<iXaml:NodeLayout Key="Locations" TargetTypeName="LocationViewModel" DisplayMemberPath="{Binding Name}"/>
<iXaml:NodeLayout Key="ChildLocations" TargetTypeName="string"/>
</iXaml:XamDataTree.GlobalNodeLayouts>
</iXaml:XamDataTree>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</iWPF:XamComboEditor.ComboBoxStyle>
</iWPF:XamComboEditor>
You can also achieve this without naming your controls with the RelativeSource property in the Binding (something like RelativeSource={RelativeSource AncestorType=iWPF:XamComboEditor}).

Generate a DataGrid from a List using MVVM

I am trying to create a data grid based on the items of a list of objects.
I have the following class:
class BookCopies
{
private string bookTitle;
private int bookNumbers;
public string BookTitle
{
get;
set;
}
public int BookNumbers
{
get;
set;
}
}
and then i populate a classical list with items of this type
List<BookCopies> booksWithCopies = new List<BookCopies>();
//...
return booksWithCopies;
Basically this list will contain items like ("The Adventures of Tom Sawyer", 3) , ("Infinite Jest" , 8) and so on... It keeps a book title and the number of books from the bookstore.
I know that this list has to be converted to a ObservableCollection and raise the NotifyPropertyChanged in order to have the mvvm pattern as it should.
Now what I am trying is to use this book list to set the content of the grid view. So the book title to become the column header and below it to have the number of books. Basically the grid to look something like this:
Most of the examples where hard coding the header of the column, now I am trying to read it from a list, and also to bound the content of the grid from the same list.
I am quite new to WPF and also MVVM and first I am thinking if this is possible (it will be nice to... since I have all the data that I need already in a list) and if that is possible to send me some examples or quide me through a little bit about how to implement this.
<DataGrid x:Name="Dgrd" HeadersVisibility="Row" VerticalGridLinesBrush="LawnGreen" CanUserAddRows="False">
<DataGrid.Resources>
<Style TargetType="DataGridRowHeader">
<Setter Property="BorderBrush" Value="DarkViolet"/>
<Setter Property="BorderThickness" Value="1"/>
</Style>
<Style TargetType="DataGridCellsPresenter">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="DataGridCellsPresenter">
<StackPanel>
<Label Padding="5" Content="{Binding BookTitle}" BorderThickness="0 0 0 1" FontWeight="Bold" BorderBrush="DarkViolet" Background="Transparent"/>
<TextBlock Padding="5" Text="{Binding BookNumbers}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
<DataGrid.ItemsPanel>
<ItemsPanelTemplate>
<DataGridRowsPresenter Orientation="Horizontal" />
</ItemsPanelTemplate>
</DataGrid.ItemsPanel>
</DataGrid>
You can not achieve what you are trying to because DataGrid does not work that way. The bounded collection on the DataGrid (itemsource) is used to populate the row of the DataGrid.
However there are others trying to do the same thing you are. Take a look on WPF horizontal DataGrid, the result is similar to what you are describing (except the column names on the left). The resulting solution requires some coding.
Another resource you can look into is the Displaying vertical rows in a DataGrid from Code Project website.

Passing Non-Item Values to Properties in ItemTemplate

Today I'm having trouble passing values from a parent control down to the properties of a child control in a list.
I have a custom control which I've made which functions as a Thumbnail Check Box. Essentially it's just a checkbox wrapped around an image with some nice borders. It's all wrapped up into a DLL and deployed as a custom control
If I want to use a single instance of the control, I can do so like this...
<tcb:ThumbnailCheckBox IsChecked="True"
ImagePath="D:\Pictures\123.jpg"
CornerRadius="10"
Height="{Binding ThumbnailSize}"
Margin="10" />
Code Listing 1 - Single Use
This works great, and easily binds to ThumbnailSize on my ViewModel so I can change the size of the image in the control however I want.
The problem is when I want to expand the use of this control into a list, I'm running into a few problems.
To begin, I've styled the ListBox control to meet my needs like so...
<Style TargetType="{x:Type ListBox}"
x:Key="WrappingImageListBox">
<!-- Set the ItemTemplate of the ListBox to a DataTemplate
which explains how to display an object of type BitmapImage. -->
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{TemplateBinding utilities:MyAttachedProperties.ImageSize}"
CornerRadius="8"
Margin="10">
</tcb:ThumbnailCheckBox>
</DataTemplate>
</Setter.Value>
</Setter>
<!-- Swap out the default items panel with a WrapPanel so that
the images will be arranged with a different layout. -->
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<!-- Set this attached property to 'Disabled' so that the
ScrollViewer in the ListBox will never show a horizontal
scrollbar, and the WrapPanel it contains will be constrained
to the width of the ScrollViewer's viewable surface. -->
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility"
Value="Disabled" />
</Style>
Code Listing 2 - ListBox Style
And I call it like this from my main view...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent"
util:MyAttachedProperties.ImageSize="500"/>
Code Listing 3 - Main Call
This works exactly as I'd like, except for the ImageSize property. Both ImagePath and Selected are properties of the individual list items being bound to the ListBox.
As you can see, I created an attached property to try to pass the value (500), but it doesn't seem to be working. I should note that I think the style I've created is correct because the elements use the default value.
public static class MyAttachedProperties
{
public static double GetImageSize(DependencyObject obj)
{
return (double)obj.GetValue(ImageSizeProperty);
}
public static void SetImageSize(DependencyObject obj, double value)
{
obj.SetValue(ImageSizeProperty, value);
}
public static readonly DependencyProperty ImageSizeProperty =
DependencyProperty.RegisterAttached(
"ImageSize",
typeof(double),
typeof(MyAttachedProperties),
new FrameworkPropertyMetadata(50D));
}
Code Listing 4 - Attached Property
The 50D specified on the last line is applying to the listed control. If I change it, and recompile, the end result changes. But the sent value of 500 I specified in my ListBox Main call (listing 3) is not ever sent. Of course, I would eventually like to change the 500 into a bound property on my view model, but I won't do that until I get it working with an explicit value.
Can someone help me figure out how to send a value from my main ListBox call (listing 3) and apply it to the individual items that are populated by the template? The other properties I have work, but they are a properties of each item in the List I'm binding to the ListBox, whereas ImageSize is not.
EDIT To address First Response
This seems to be working, but it's kind of peculiar. My listbox is now being called like so...
<ListBox ItemsSource="{Binding DirectoryPictures}"
Grid.Row="1"
Style="{DynamicResource WrappingImageListBox}"
Background="Transparent" />
And I've changed my style to the code you suggested...
<tcb:ThumbnailCheckBox ImagePath="{Binding ImagePath}"
IsChecked="{Binding Selected}"
Height="{Binding Path=DataContext.ThumbnailSize, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBox}}}"
CornerRadius="8"
Margin="10">
My only concern is, now the style is accessing the ViewModel for that control directly rather than receiving a bound value.
Suppose I wanted to use the ListBox again, but on another UserControl whose ViewModel didn't have ThumbnailSize property, but used one by another name?
You see where I'm going with this... the current solution is not very extensible and is limited to the current classes as they are named exactly.
In fact, in a perfect world, I'd like to have variable names for the ImagePath and Selected properties, but that's a different discussion.
It's possible to use FindAncestor. The idea of that is, child traverses through logical tree, and tries to find parent with concrete type (in this case, ListBox), and then accesses attached property. See http://wpftutorial.net/BindingExpressions.html for more binding expressions.
In your ItemTemplate, this is how you could access ThumbnailSize property:
{Binding Path=(util:MyAttachedProperties.ImageSize),
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type ListBox}}}
Essentially, the question asked here was a little bit opposite, but results are same. "How could items in ListBox access ListBox (attached) properties.

How to find a data-binded ListBoxItem position?

I have a ListBox that its ItemsSource is given from a class based on the data binded items template. I want to find ListBox.SelectedItem position relative to the ListBox. Since I've used a class to feed ItemsSource, I'm not be able to cast ListBox.SelectedItem (which has a type of object) to the ListBoxItem. (Instead I should cast it to the source class type.)
What's the way? -Thanks
Details: (Arbitrary)
There is a ListBox which implements a Style like so:
<Style x:Key="MyListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<Border ...>
<StackPanel ...>
<Image Source="{Binding Path=ItemImageSource}" .../>
<TextBlock Text="{Binding Path=ItemTitle}" .../>
</StackPanel>
</Border>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
The ListBox has been used as follows:
<ListBox x:Name="MyListBox"
ItemsSource="{Binding}"
Style="{StaticResource ResourceKey=MyListBoxStyle}"/>
Also there is a class that supports MyListBox data-binding info:
internal class MyListBoxItemBinding
{
public string ItemTitle { get; set; }
public ImageSource ItemImageSource { get; set; }
}
And to feed the MyListBox:
MyListBox.ItemsSource = new List<MyListBoxItemBinding> { /* some items */ };
Now, how can I find MyListBox.SelectedItem location relative to the MyListBox?
Use ItemsControl.ItemContainerGenerator to get a reference to the item container generator for your ListBox (this is the object that creates wrappers for all your databound objects).
Then, use the ItemContainerGenerator.ContainerFromItem method to get a reference to the UIElement that represents the selected ListBoxItem.
Finally, see the answer to this question to for a way of getting the coordinates of the selected item relative to the ListBox.

Categories