WPF Combobox always displays icon of the first element - wpf

I have a combobox in a custom window where I display a language short name its corresponding icon
<ComboBox SelectionChanged="LanguagesListBoxSelectionChanged" WindowChrome.IsHitTestVisibleInChrome="True"
Visibility="{TemplateBinding IsLanguageSwitchComboBoxVisible}"
ItemsSource="{Binding RelativeSource={RelativeSource TemplatedParent}, UpdateSourceTrigger=PropertyChanged, Path=Languages}"
IsEnabled="True" DockPanel.Dock="Right" Width="90" Height="28"
IsSynchronizedWithCurrentItem="True"
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= SelectedLanguage}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<TextBlock FontSize="24" VerticalAlignment="Center" Text="{Binding LanguageName}" Margin="0 0 4 0"></TextBlock>
<Image Height="24" Width="24">
<Image.Source>
<BitmapImage UriSource="{Binding LanguagePicture, Converter={StaticResource DebugDummyConverter}}" />
</Image.Source>
</Image>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
Custom window:
public class CustomTitleBarWindow : Window
{
public CustomTitleBarWindow()
{
Languages = new List<LanguageModel>()
{
new() {Id="ru", LanguageName = "RU", LanguagePicture = "../../Images/ru.ico" },
new() {Id="en", LanguageName = "EN", LanguagePicture = "../../Images/en.ico" }
};
SelectedLanguage = Languages.First();
}
public static readonly DependencyProperty SelectedLanguageProperty =
DependencyProperty.Register("SelectedLanguage", typeof(LanguageModel), typeof(CustomTitleBarWindow), new PropertyMetadata(null));
public LanguageModel SelectedLanguage
{
get => (LanguageModel)GetValue(SelectedLanguageProperty);
set => SetValue(SelectedLanguageProperty, value);
}
...
Item list displays correctly.
The issue is when I select any item, the combobox displays a proper text ("EN" or "RU") but the icon displayed is the one at "../../Images/ru.ico" path.
If I put "../../Images/en.ico" into the list first then the "english" icon will be displayed no matter what item was selected.

Solved:
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel VerticalAlignment="Bottom" Orientation="Horizontal">
<TextBlock FontSize="24" VerticalAlignment="Center" Text="{Binding LanguageName}" Margin="0 0 4 0"></TextBlock>
<Image Height="24" Width="24" Source="{Binding LanguagePicture, Converter={StaticResource ImageConverter}}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
And an ImageConverter
public class ImageConverter : IValueConverter
{
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
return new BitmapImage(new Uri(value.ToString(), UriKind.Relative));
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
The problem is that BitmapImage needs a value immediately whereas binding provides it later.

Related

ComboBox KeyValuePair Binding WPF - Display Member

i got a quick question about binding the DisplayMember of my ComboBox.
I have a list with a KeyValuePair for example:
1, Value1;
2, Value2;
3, Value3;
My SelectedValuePath is set to Key which is in my example "1".
Now i want my DisplayMemberPath to show "Key - Value" so for example the Textbox should show "1 - Value1".
Is that possible?
Thank's in advance!
If your ComboBox is not editable, you can create a DataTemplate for your key-value pairs.
<ComboBox ...>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Key, Mode=OneWay}"/>
<Run Text=" - "/>
<Run Text="{Binding Value, Mode=OneWay}"/>
</TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
You can do for instance so:
<ComboBox x:Name="cmb1" ItemsSource="{Binding YourDictionary}" SelectedValuePath="Key">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Key}"/>
<TextBlock Text="-"/>
<TextBlock Text="{Binding Value}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Text="{Binding SelectedValue, ElementName=cmb1}"/>
One more way you can go is to use value converter:
<ComboBox x:Name="cmb1" ItemsSource="{Binding YourDictionary}" SelectedValuePath="Key">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource YourConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBox Text="{Binding SelectedValue, ElementName=cmb1}"/>
public class KeyValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is KeyValuePair<int, object> obj)//use your types here
{
return obj.Key.ToString() + "-" + obj.Value.ToString();
}
return value;
}
public object ConvertBack(object value, Type targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException("One way converter.");
}
}

Bind a formula to a TextBox

I'm doing a little program that needs to calculate the thickness of a book using its page count.
Please note that I'm almost completely new to programing. And this is my first time on ths website.
Here's the XAML.
<StackPanel VerticalAlignment="Center" Margin="25 5 0 10">
<Slider Maximum="800" TickPlacement="BottomRight" TickFrequency="1"
IsSnapToTickEnabled="True" DockPanel.Dock="Right" x:Name="slNumPages"
Margin="0" LargeChange="16" SmallChange="2" Value="200" Minimum="16"
BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}" Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness" Text="..."/>
</WrapPanel>
How can I bind the TextBlock tbApproxThickness to a formula where the value of slider slNumPages gets multiplied by constant 0.0252?
here you go
<StackPanel VerticalAlignment="Center"
Margin="25 5 0 10"
xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<l:ApproxThicknessConverter x:Key="ApproxThicknessConverter" />
</StackPanel.Resources>
<Slider Maximum="800"
TickPlacement="BottomRight"
TickFrequency="1"
IsSnapToTickEnabled="True"
DockPanel.Dock="Right"
x:Name="slNumPages"
Margin="0"
LargeChange="16"
SmallChange="2"
Value="200"
Minimum="16"
BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}"
Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness"
Text="{Binding Value,ElementName=slNumPages,Converter={StaticResource ApproxThicknessConverter}}" />
</WrapPanel>
</StackPanel>
following are the changed for the same
<l:ApproxThicknessConverter x:Key="ApproxThicknessConverter" />
Text="{Binding Value,ElementName=slNumPages,Converter={StaticResource ApproxThicknessConverter}}"
converter class
namespace CSharpWPF
{
class ApproxThicknessConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value * 0.0252;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
You may need to refer to the Data Binding Overview page on MSDN to understand this answer. One simple solution would be to data bind a double property to the Slider.Value property and then reference this property in a formula property that does the calculation. Try something like this:
public double SliderValue
{
get { return sliderValue; }
set
{
sliderValue = value;
NotifyPropertyChanged("SliderValue"); // Implement INotifyPropertyChanged
FormulaValue = sliderValue * 0.0252;
}
}
public double FormulaValue { get; set; } // Implement INotifyPropertyChanged
...
<Slider Maximum="800" TickPlacement="BottomRight" TickFrequency="1"
IsSnapToTickEnabled="True" DockPanel.Dock="Right" x:Name="slNumPages"
Margin="0" LargeChange="16" SmallChange="2" Value="{Binding SliderValue}"
Minimum="16" BorderThickness="0" />
<WrapPanel>
<TextBox Text="{Binding ElementName=slNumPages, Path=Value,
UpdateSourceTrigger=PropertyChanged}" Width="40" />
<TextBlock Text="Approx. thickness: " />
<TextBlock x:Name="tbApproxThickness" Text="{Binding FormulaValue}" />
</WrapPanel>
Please bear in mind that you will need to implement the INotifyPropertyChanged Interface on these properties to make this work. You will also need to set the DataContext of the Window properly. Having done that, each time the Slider is moved, the property will automatically update the FormulaValue.

Show tick image when item is selected in WPF Listbox

I have this simple ListBox which displays a list of images horizontal en vertical.
I have also added a tick image on every image. Now I would like to enable this tick image only when the item is selected in the Listbox.
How can I achieve this?
ListBox XAML:
<ListBox x:Name="PhotoCollection"
Grid.Row="2"
Grid.ColumnSpan="4"
ItemsSource="{Binding PhotoCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
SelectionMode="Multiple">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<Border BorderBrush="White"
BorderThickness="2"
Margin="5"
Background="LightGray">
<Grid>
<Image Source="{Binding}"
Stretch="Uniform"
Width="50"
Height="50"
Margin="5" />
<Image Source="{StaticResource Check_24}"
Visibility="{Binding Converter={StaticResource VisibleConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1},Path=IsSelected}"
Stretch="Uniform"
Width="20"
Height="20"
Margin="5"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"/>
</Grid>
</Border>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel IsItemsHost="True"
Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
EDIT: This line does the trick
Visibility="{Binding Converter={StaticResource VisibleConverter}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}, AncestorLevel=1},Path=IsSelected}"
It should work when you bind the IsSelected property of the ListBoxItem to your property IsVisible. But it depends on how you have implemented your ViewModel and the properties.
<ListBox>
<!-- the rest of the XAML-Definition of your ListBox -->
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding IsVisible, Mode=TwoWay}"/>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
When I need to bind visibility of items to boolean values, I've been using a converter class:
public class BoolToVisibleOrHidden : IValueConverter {
public BoolToVisibleOrHidden() { }
public bool Collapse { get; set; }
public bool Reverse { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
bool bValue = (bool)value;
if (bValue != Reverse) {
return Visibility.Visible;
} else {
if (Collapse)
return Visibility.Collapsed;
else
return Visibility.Hidden;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
Visibility visibility = (Visibility)value;
if (visibility == Visibility.Visible)
return !Reverse;
else
return Reverse;
}
}
After grabbing an instance of it in XAML like so :
<local:BoolToVisibleOrHidden x:Key="BoolToVisConverter" Collapse="True" />
I can bind visibility of items like this:
Visibility="{Binding Converter={StaticResource BoolToVisConverter}, Path=DataContext.PATHTOBOOLEAN}"

WPF nullreference exception thrown on Button when value converter in prior Label is in place

TLDR:
A Nullreference exception is thrown when XAML parsing any button element when another unrelated element has databinding and value converter. When the buttons are commented out, or the databinding is removed the form works.
I have a WPF UserControl with a list box in it which has a DataTemplate with multiple controls in it. I also have a bool to visibility value converter which I use in different locations in the control. I added a new static reference of the converter to the control (different bool to visibility values) and bind it to a label and suddenly the app crashes upon loading the control.
I remove the binding and all is well again. The converter is not at fault though; I put break points in its constructor as well as the convert methods, and it never reaches it. The exception is in the parsing of the XAML, not at the label, but at the first button declared, which is 100% unrelated to the label. If I remove the value converter binding from the label, the XAML parses correctly and the button has no issues.
However, to complicate things, if I comment out that button and every other button in the XAML, it also parses correctly and the value converter works without a problem.
What am I missing?
XAML:
<UserControl x:Class="Customer_Management.OpportunityControl"
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"
xmlns:l="clr-namespace:Customer_Management"
d:DesignHeight="300" d:DesignWidth="300" MaxHeight="200" BorderBrush="DarkGray" BorderThickness="1" x:Name="ucOpp">
<UserControl.Resources>
<l:NullToVisibilityConverter NullValue="Hidden" NonNullValue="Visible" x:Key="NullToHidden"></l:NullToVisibilityConverter>
<l:BoolToVisibilityConverter TrueValue="Visible" FalseValue="Hidden" x:Key="TrueToVisible"></l:BoolToVisibilityConverter>
<l:BoolToVisibilityConverter TrueValue="Hidden" FalseValue="Visible" x:Key="FalseToVisible"></l:BoolToVisibilityConverter>
</UserControl.Resources>
<ScrollViewer>
<ListBox Name="lbxOpps" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Border BorderBrush="DarkGray" BorderThickness="1">
<Grid>
<StackPanel>
<TextBlock Text="{Binding Path=Opportunity.Name}" Margin="0,1,3,1"></TextBlock>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Opportunity.Amount, StringFormat=\{0:C\}}" Margin="0,1,3,1"></TextBlock>
<Button Name="btnFinishOrder" Click="btnFinishOrder_Click">Finish Order</Button>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Margin="0,1,3,1">Invoice #</TextBlock>
<TextBox Name="tbxInvoiceNumber" Text="{Binding Path=InvoiceNumber}"></TextBox>
</StackPanel>
<ListBox ItemsSource="{Binding Path=Batches}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<StackPanel Orientation="Horizontal">
<Label FontWeight="Bold" Visibility="{Binding Path=IsOcc, Converter={StaticResource TrueToVisible}}">OCC #:</Label>
<ComboBox Margin="0,0,0,0" Name="cboLicense" SelectedValue="{Binding Path=SLicense}" DisplayMemberPath="LicenseID"
SelectedValuePath="LicenseID" ItemsSource="{Binding ElementName=ucOpp, Path=Licenses}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cboLicense}" Click="Button_ClearContorl">X</Button>-->
<Label Margin="0,0,3,0" >R #:</Label>
<!--<Button ToolTip="Click to change" Name="btnLicFile" Click="Button_Click" >LIC File</Button>-->
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=btnLicFile}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
<Label Margin="0,0,3,0" >P #:</Label>
<ComboBox Margin="0,0,0,0" Name="cbxPNum" SelectedValue="{Binding Path=PNum}" DisplayMemberPath="Name"
SelectedValuePath="Id" ItemsSource="{Binding ElementName=ucOpp, Path=Nums}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cbxPNum}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
<Label Margin="0,0,3,0" >U #:</Label>
<ComboBox Margin="0,0,0,0" Name="cbxUNum" SelectedValue="{Binding Path=UNum}" DisplayMemberPath="Name"
SelectedValuePath="Id" ItemsSource="{Binding ElementName=ucOpp, Path=Nums}">
</ComboBox>
<!--<Button Margin="0,0,3,0" DataContext="{Binding ElementName=cbxUNum}" Click="Button_ClearContorl" ToolTip="Clear">X</Button>-->
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
<Label HorizontalAlignment="Stretch" Background="LawnGreen" FontSize="24" Opacity="0.8" VerticalAlignment="Stretch" Visibility="{Binding Path=IsProcessing, Converter={StaticResource TrueToVisible}}"></Label>
<Label HorizontalAlignment="Center" FontSize="24" VerticalAlignment="Center" Visibility="{Binding Path=IsProcessing, Converter={StaticResource TrueToVisible}}">Processing...</Label>
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</ScrollViewer>
ValueConverter:
public sealed class BoolToVisibilityConverter : IValueConverter
{
public Visibility TrueValue { get; set; }
public Visibility FalseValue { get; set; }
public BoolToVisibilityConverter()
{
// set defaults
TrueValue = Visibility.Visible;
FalseValue = Visibility.Collapsed;
}
public object Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (!(value is bool))
return null;
return (bool)value ? TrueValue : FalseValue;
}
public object ConvertBack(object value, Type targetType,
object parameter, CultureInfo culture)
{
if (Equals(value, TrueValue))
return true;
if (Equals(value, FalseValue))
return false;
return null;
}
}

Embed DataGrid into WPF Treeview nodes

I need to display Hierarchy in the treeview. But Details should be displayed in the datagrid.
How do I have to write my template to achieve this? I misunderstand smth in templates for now.
<TreeView ItemsSource="{Binding Path=Categories}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type stackProjects:Category}" ItemsSource="{Binding Path=SubCategories}">
<TextBlock Margin="3" Text="{Binding Path=CategoryName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type stackProjects:SubCategory}" ItemsSource="{Binding Path=Details}">
<TextBlock Text="{Binding Path=SubCategoryName}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type stackProjects:Detail}" >
<StackPanel Orientation="Horizontal">
<TextBlock Margin="3" Text="{Binding Path=Name}"/>
<TextBlock Margin="3" Text=" - "/>
<TextBlock Margin="3" Text="{Binding Path=Info}"/>
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
I've found a workaround. I had to understand that Details should be presented as a collection within a single element which has IEnumerable property. May be it's not the best solution but it works.
I needed to create a Converter to wrap my collection into single one.
public class BoxingItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var values = value as IEnumerable;
var type = parameter as Type;
if (values == null || type == null)
return null;
if (type.GetInterfaces().Any(x => x == typeof (IItemsWrapper)))
{
var instance = (IItemsWrapper) type.Assembly.CreateInstance(type.FullName);
instance.Items = (IEnumerable) value;
//returned value should be IEnumerable with one element.
//Otherwise we will not see children nodes
return new List<IItemsWrapper> {instance};
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Example for wrappers:
internal interface IItemsWrapper
{
IEnumerable Items { get; set; }
}
public class ItemsWrapper : IItemsWrapper
{
public IEnumerable Items { get; set; }
}
public class DetailItemsWrapper : ItemsWrapper{}
public class InvoiceItemsWrapper:ItemsWrapper{}
And the xaml. It will not require a lot of changes. You just need to use Boxing converter and set the Type to return in the converter parameter.
<TreeView ItemsSource="{Binding Path=Categories}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type wpfProj:Category}" ItemsSource="{Binding Path=SubCategories}">
<TextBlock Margin="4" Text="{Binding Path=CategoryName}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type wpfProj:SubCategory}"
ItemsSource="{Binding Path=Details, Converter={StaticResource boxingItems}, ConverterParameter={x:Type wpfProj:DetailItemsWrapper}}" >
<StackPanel>
<TextBlock Margin="4" Text="{Binding Path=SubCategoryName}"/>
</StackPanel>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type wpfProj:DetailItemsWrapper}" >
<DataGrid ItemsSource="{Binding Path=Items}"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
I've uploaded sample to dropbox.
Here is how it does look like:

Resources