I bind the Rectangle Fill property on bool value (Fill="{Binding Path=IsSelected, Converter={StaticResource rectangleFillConverter}}") and it throws a null exception. I checked the value of property (IsSelected) is not null. When I remove Converter from Fill property it works. Here is my code:
xaml
<Rectangle Width="{Binding Duration}" Height="20" Tag="{Binding .}" IsEnabled="{Binding Path=IsEnabled}" Fill="{Binding Path=IsSelected, Converter={StaticResource rectangleFillConverter}}" ToolTip="{Binding Path=Shift}" MouseDown="LabelShift_MouseDown"></Rectangle>
Converter
public class RectangleControlFillConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush;
bool b= (bool)value;
if (b)
brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5C8FFF"));
else
brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#73E34D"));
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Static Resource
<converters:RectangleControlFillConverter x:Key="rectangleFillConverter"/>
Property
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
ItemsControl where is rectangle and this converter works Converter={StaticResource timeToPositionConverter}}"
<ItemsControl Name="icSchedule" ItemsSource="{Binding .}" Grid.Row="1" Grid.Column="1">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Margin="0"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding Path=icw}" Tag="{Binding .}" Margin="0,10,0,0"><!--Margin="3"Grid.Row="1" Grid.Column="1" Background="DarkGray" BorderThickness="1" BorderBrush="White"-->
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="Canvas.Left" Value="{Binding Path=Start, Converter={StaticResource timeToPositionConverter}}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Index}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="TimeLineEntry">
<Border BorderThickness="1" BorderBrush="DarkGray">
<Rectangle Width="{Binding Duration}" Height="20" Tag="{Binding .}" IsEnabled="{Binding Path=IsEnabled}" Fill="{Binding Path=IsSelected, Converter={StaticResource rectangleFillConverter}}" ToolTip="{Binding Path=Shift}" MouseDown="LabelShift_MouseDown">
</Rectangle>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
This is the class on wich object is rectangle binding (icw is a list of that objects)
public partial class ScheduleItem
{
public string Shift
{
get
{
//string s = ((DateTime)DateFrom).ToString("dd.MM.yyyy") + " " + ((TimeSpan)TimeFrom).ToString() + " - " + ((DateTime)DateTo).ToString("dd.MM.yyyy") + " " + ((TimeSpan)TimeTo).ToString();
String s = String.Format("{0}.{1} {2}:{3} - {4}.{5} {6}:{7}", FullDateFrom.Day, FullDateFrom.Month, FullDateFrom.Hour, FullDateFrom.Minute, FullDateTo.Day, FullDateTo.Month, FullDateTo.Hour, FullDateTo.Minute);
return s;
}
}
private DateTime FullDateFrom
{
get
{
DateTime dt = ((DateTime)DateFrom).AddHours(((TimeSpan)TimeFrom).Hours).AddMinutes(((TimeSpan)TimeFrom).Minutes);
return dt;
}
}
private DateTime FullDateTo
{
get
{
DateTime dt = ((DateTime)DateTo).AddHours(((TimeSpan)TimeTo).Hours).AddMinutes(((TimeSpan)TimeTo).Minutes);
return dt;
}
}
public string Name { get; set; }
public DateTime Start { get { return FullDateFrom; } }
private int index;
public int Index
{
get
{
if (CampaignPerson != null)
return CampaignPerson.Index;
else
return index;
}
set
{
index = value;
}
}
public int Duration
{
get
{
TimeSpan dt = FullDateTo - FullDateFrom;
return (dt.Days* 92) + (dt.Hours*4);
}
}
public bool IsEnabled
{
get { return (FullDateFrom > DateTime.Now); }
}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
OnPropertyChanged("IsSelected");
}
}
#region setters
partial void OnTimeFromChanged()
{
OnPropertyChanged("Duration");
OnPropertyChanged("Start");
}
partial void OnTimeToChanged()
{
OnPropertyChanged("Duration");
OnPropertyChanged("Start");
}
partial void OnDateFromChanged()
{
OnPropertyChanged("Duration");
OnPropertyChanged("Start");
}
partial void OnDateToChanged()
{
OnPropertyChanged("Duration");
OnPropertyChanged("Start");
}
#endregion
}
ViewModel Class The icShedule is Binding on collection of this Class
public class ScheduleExtension
{
public ICollectionView icw {get; set;}
public ScheduleExtension(CampaignPerson cp)
{
campainPerson = cp;
scheduleItemsList.CollectionChanged += new NotifyCollectionChangedEventHandler(_scheduleItemsList_CollectionChanged);
icw = CollectionViewSource.GetDefaultView(scheduleItemsList);
icw.Filter = ScheduleFilter;
}
private CampaignPerson _campainPerson;
public CampaignPerson campainPerson
{
get { return _campainPerson; }
set
{
_campainPerson = value;
scheduleItemsList = new ObservableCollection<ScheduleItem>(_campainPerson.ScheduleItem.Where(p=>p.active));
}
}
private DateTime _currentDate;
public DateTime CurrentDate
{
get { return _currentDate; }
set
{
_currentDate = value;
icw.Refresh();
//CurrrentScheduleItemsList = new ObservableCollection<ScheduleItem>(scheduleItemsList.Where(p => p.active && ((DateTime)p.DateFrom).Month == CurrentDate.Month && ((DateTime)p.DateFrom).Year == CurrentDate.Year));
//OnPropertyChanged("CurrentScheduleItemsList");
}
}
public ObservableCollection<ScheduleItem> scheduleItemsList;
void _scheduleItemsList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
}
private bool ScheduleFilter(object item)
{
if (CurrentDate != null)
{
ScheduleItem si = item as ScheduleItem;
return (CurrentDate.Month == ((DateTime)si.DateFrom).Month && CurrentDate.Year == ((DateTime)si.DateFrom).Year);
}
return false;
}
}
Try changing the <DataTemplate DataType="TimeLineEntry"> to <DataTemplate DataType="ScheduleItem">. Maybe ScheduleItem is a TimeLineEntry (you posted only one part of a partial class), but try if it works.
The problem could also be caused by the way you are handling the collection. In the constructor you say:
icw = CollectionViewSource.GetDefaultView(scheduleItemsList);
icw.Filter = ScheduleFilter;
And then in the campainPerson property you say:
scheduleItemsList = new ObservableCollection<ScheduleItem>(_campainPerson.ScheduleItem.Where(p=>p.active));
You have to either use the same collection object (by clearing it and adding new items), or by creating new ICollectionView and assigning it to icw (so you'd have to repeat the two lines from constructor after creating new ObservableCollection). Try that too.
I dont think problem is from converter, even if all work fine when you remove it. This is my test app.
MainWindow.xaml
<Window x:Class="MVVMTests.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:MVVMTests"
Background="{StaticResource {x:Static SystemColors.ControlBrushKey}}"
>
<Window.Resources>
<ResourceDictionary>
<local:RectangleControlFillConverter x:Key="rectangleFillConverter" />
</ResourceDictionary>
</Window.Resources>
<Window.DataContext>
<local:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<CheckBox Content="toto" IsChecked="{Binding IsSelected}" />
<Rectangle Fill="{Binding Path=IsSelected, Converter={StaticResource rectangleFillConverter}}" Grid.Row="1" />
</Grid>
</Window>
The ViewModel
namespace MVVMTests
{
public class MainWindowViewModel : NotificationObject
{
private Boolean isSelected;
public MainWindowViewModel()
{
isSelected = true;
}
public Boolean IsSelected
{
get { return this.isSelected; }
set
{
this.isSelected = value;
this.RaisePropertyChanged<Boolean>(() => IsSelected);
}
}
}
}
and the conveter
namespace MVVMTests
{
[ValueConversion(typeof(Boolean), typeof(SolidColorBrush))]
public class RectangleControlFillConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
SolidColorBrush brush;
bool b = (bool)value;
if (b)
brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5C8FFF"));
else
brush = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#73E34D"));
return brush;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
With this simple code, we can see that the converter is not the problem (window switch ugly blue / ugly green), and binding works fine.
That's not a solution, but that's show us the problem is elsewhere ...
Converter in rectangle still doesnt work on any property.
This is working for me Fill="{Binding Path=CurrentColor}"
and this change in ScheduleItem
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if(_isSelected)
CurrentColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#73E34D"));
else
CurrentColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5C8FFF"));
OnPropertyChanged("CurrentColor");
}
}
private SolidColorBrush _currentColor = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#5C8FFF"));
public SolidColorBrush CurrentColor
{
get { return _currentColor; }
set { _currentColor = value; }
}
Thanks guys for your time and your suggestion.
Related
This question already has answers here:
How to use DataTemplateSelector with ContentControl to display different controls based on the view-model?
(2 answers)
Closed 4 years ago.
I'm trying to write a simple dialog that would accept a value in a SpinEdit or a text in a TextEdit. I'm using multiple VMs and I made a selector that should view a proper control based on the logic in the c++/cli file.
XAML:
xmlns:local="clr-namespace:asd"
Title="{Binding Path=Title, Mode=OneTime}"
<dx:DXWindow.Resources>
<DataTemplate x:Key="TInputValueVM" DataType="{x:Type local:TInputValueVM}">
<dxe:SpinEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}" />
</DataTemplate>
<DataTemplate x:Key="TInputTextVM" DataType="{x:Type local:TInputTextVM}">
<dxe:TextEdit Height="23" Width="200"
Text="{Binding Value, Mode=TwoWay}"
MaskType="RegEx" Mask="{Binding Mask, Mode=OneWay}"
MaxLength="{Binding Path=InputLength}"/>
</DataTemplate>
<local:PropertyDataTemplateSelector x:Key="templateSelector"
DataTemplate_Value="{StaticResource TInputValueVM}"
DataTemplate_Text="{StaticResource TInputTextVM}" />
</dx:DXWindow.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" >
<Label x:Uid="Label" MinHeight="24" MinWidth="60" Content="Value" />
<ContentControl Content="{Binding Path=Whoami}" ContentTemplateSelector="{StaticResource templateSelector}" />
</StackPanel>
<StackPanel Grid.Row="1" x:Uid="OKCancel_Buttons" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<Button Height="23" x:Name="OK_Button" Click="OK_Click" Content="OK" IsDefault="True" HorizontalAlignment="Right" MinWidth="95" />
<Button Height="23" x:Name="Cancel_Button" Click="Cancel_Click" Content="Cancel" HorizontalAlignment="Right" MinWidth="95" />
</StackPanel>
</Grid>
In c# I have a base VM and two VMS that extend it, one for values and one for text. The rest of the properties stay the same.
C#
namespace asd
{
public class TInputBaseVM : ViewModelBase
{
private string m_sTitle;
private string m_sMask;
private int m_nInputLenght;
private string m_sWhoami;
public TInputBaseVM(string A_sTitle, string A_sMask, int A_nInputLength)
{
m_sTitle = A_sTitle;
m_sMask = A_sMask;
m_nInputLenght = A_nInputLength;
}
protected string Title
{
get { return m_sTitle; }
set { SetProperty(ref m_sTitle, value, () => Title); }
}
protected string Mask
{
get { return m_sMask; }
set { SetProperty(ref m_sMask, value, () => Mask); }
}
protected int InputLength
{
get { return m_nInputLenght; }
set { SetProperty(ref m_nInputLenght, value, () => InputLength); }
}
protected string Whoami
{
get { return m_sWhoami; }
set { SetProperty(ref m_sWhoami, value, () => Whoami); }
}
}
public class TInputValueVM : TInputBaseVM
{
public TInputValueVM(string A_sTitle, string A_sMask, int A_nInputLength, double A_nValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_nValue;
Whoami = "Value";
}
private double m_nValue;
public double Value
{
get { return m_nValue; }
set { SetProperty(ref m_nValue, value, () => Value); }
}
}
public class TInputTextVM : TInputBaseVM
{
public TInputTextVM(string A_sTitle, string A_sMask, int A_nInputLength, string A_sValue) : base(A_sTitle, A_sMask, A_nInputLength)
{
Value = A_sValue;
Whoami = "Text";
}
private string m_sValue;
public string Value
{
get { return m_sValue; }
set { SetProperty(ref m_sValue, value, () => Value); }
}
}
public class PropertyDataTemplateSelector : DataTemplateSelector
{
public DataTemplate DataTemplate_Value { get; set; }
public DataTemplate DataTemplate_Text { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
var selector = item as string;
if(selector == "Value")
return DataTemplate_Value;
return DataTemplate_Text;
}
}
}
In c++/cli I create an object of a proper VM and I'd like the WPF to automatically update the view to either spinedit or textedit, however I'm not sure how to properly bind the properties from the C#. If I explicitly type 'Value' in the Content property of the ContentControl then it displays the spinEdit but I don't know how to bind it so it automatically takes the correct property.
EDIT: I'm adding c++/cli code to show how I choose different VMs
C++/cli:
bool TSignalNumberPositionDialogCLR::StartDialog(TSignalNumberPositionSupport& A_Attributes, HWND A_hwndParent, LPTSTR String)
{
try
{
TInputValueVM ^oExchange_Value;
TInputTextVM ^oExchange_Text;
int inputFormat = A_Attributes.GetInputFormat();
if(inputFormat)
oExchange_Text = gcnew TInputTextVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), gcnew System::String(A_Attributes.GetInitialText()));
else
oExchange_Value = gcnew TInputValueVM(gcnew System::String(A_Attributes.GetTitle()), gcnew System::String(A_Attributes.GetMask()),
A_Attributes.GetInputLength(), A_Attributes.GetInitialValue());
Dialogs::TSignalNumberPositionDialog^ dialog = gcnew Dialogs::TSignalNumberPositionDialog();
if(inputFormat)
dialog->DataContext = oExchange_Text;
else
dialog->DataContext = oExchange_Value;
dialog->ShowDialog();
if(dialog->DialogResult)
{
CString nValue;
if(inputFormat)
nValue = oExchange_Text->Value;
else
nValue = ((Decimal)oExchange_Value->Value).ToString("F2", CultureInfo::InvariantCulture);
A_Attributes.UpdateValue(nValue, String, A_Attributes.GetInputLength());
return true;
}
return false;
}
catch(Exception^ e)
{
e;
}
}
based on the 'inputFormat' variable I want to display different controls in the dialog.
EDIT: Based on #Clemens comments I got rid of the selector sectionand the x:Key property in the DataTemplates. I changed the content opf the Content property to Content="{Binding}" and it somehow works. The moment I create a VM it selects the correct one.
Based on your comment. Let me improve my answer. As you are facing issue in VM selection. so plesae concentrate how I assigned VM to datatemplate. Although it is done in very basic way, you can handle it if you you are using MVVM packages.
I have created 2 data template and 2 vms and each vm is bound to datatemplate. To verify, I have a combobox, which will select datatemplate based on selected value.
Here is Sample VM
public class VM : System.ComponentModel.INotifyPropertyChanged
{
private string title;
private SolidColorBrush background;
public string Title { get => title; set { title = value; RaisePropertyChanged(); } }
public SolidColorBrush Background { get => background; set { background = value; RaisePropertyChanged(); } }
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
public class VM1: VM
{
public VM1()
{
Title = "This is VM1";
Background = Brushes.Yellow;
}
}
public class VM2: VM
{
public VM2()
{
Title = "This is VM2";
Background = Brushes.Orange;
}
}
Now check for resources
<local:VM1 x:Key="VM1"/>
<local:VM2 x:Key="VM2"/>
<DataTemplate x:Key="DT1">
<Grid DataContext="{StaticResource VM1}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="DT2">
<Grid DataContext="{StaticResource VM2}">
<TextBlock Text="{Binding Title}" Background="{Binding Background}"/>
</Grid>
</DataTemplate>
<Style TargetType="ContentControl" x:Key="contentStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template1">
<Setter Property="ContentTemplate" Value="{StaticResource DT1}" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=cmbo, Path=SelectedValue}" Value="Template2">
<Setter Property="ContentTemplate" Value="{StaticResource DT2}" />
</DataTrigger>
</Style.Triggers>
</Style>
and finally I have combobox and content control just to verify
<ComboBox Name="cmbo"/>
<ContentControl Style="{StaticResource contentStyle}"/>
where cmbo.ItemsSource = new List { "Template1", "Template2" };
Hope you got the point
Im trying to learn and make the application using the MVVM principle. What Im working on is this,
When trying to implement 2 IValueConverters, only one of them is triggered i.e only one is loaded and works, the OnStockIconConverter, which is loaded when the Datagrid is initialised.
The other one, CurrencyConverter, which depends on a ComboBox toggle doesnt react on the DataGrid when I change the selected value.
I have been trying to solve this for hours, and most of the content out there is rather obscure for an MVVM beginner.
All other questions on here explain the opposite of what I want to do.
C#
namespace Coding_Dojo_3
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
///
///
public partial class MainWindow : Window
{
public ObservableCollection<StockEntryModel> StockEntriesCollection { get; }
public MainWindow()
{
InitializeComponent();
var sampleManager = new SampleManager();
StockEntriesCollection = new ObservableCollection<StockEntryModel>();
foreach (var stockEntry in sampleManager.CurrentStock.OnStock)
{
StockEntriesCollection.Add((new StockEntryModel{SoftwarePackage = stockEntry.SoftwarePackage, Amount = stockEntry.Amount}));
}
DataContext = this;
}
private void ComboBoxCurrency_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var text = ((sender as ComboBox).SelectedItem as ComboBoxItem).Content as string;
SelectedCurrency.Name = text;
Console.WriteLine("Currency changed to {0}", text);
}
}
public class StockEntryModel : INotifyPropertyChanged
{
private object _softwarePackage;
private int _amount;
public event PropertyChangedEventHandler PropertyChanged;
public object SoftwarePackage
{
get => _softwarePackage;
set
{
_softwarePackage = value;
OnPropertyChanged(nameof(SoftwarePackage));
}
}
public int Amount
{
get { return _amount; }
set
{
_amount = value;
OnPropertyChanged(nameof(Amount));
}
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class StockEntryViewModel
{
public StockEntryModel NewStockEntryModel { get; set; }
public StockEntryViewModel()
{
NewStockEntryModel = new StockEntryModel();
}
}
public static class SelectedCurrency
{
public static String Name;
}
public class CurrencyConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var currency = SelectedCurrency.Name;
switch (currency)
{
case "EUR":
return CodingDojo4DataLib.Converter.CurrencyConverter.ConvertFromEuroTo(Currencies.EUR,
(double)value);
case "USD":
return CodingDojo4DataLib.Converter.CurrencyConverter.ConvertFromEuroTo(Currencies.EUR,
(double)value);
case "GBP":
return CodingDojo4DataLib.Converter.CurrencyConverter.ConvertFromEuroTo(Currencies.EUR,
(double)value);
default:
return CodingDojo4DataLib.Converter.CurrencyConverter.ConvertFromEuroTo(Currencies.EUR,
(double)value);
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class OnStockIconConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
if ((int)value < 10)
return "red";
if ((int)value > 10 && (int)value < 20)
return "orange";
if ((int)value > 20)
return "green";
}
return "red";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML
<Window x:Class="Coding_Dojo_3.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:Coding_Dojo_3"
mc:Ignorable="d"
Title="Coding Dojo 3" Height="400" Width="1024">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="0.125*" />
<RowDefinition Height="1*" />
<RowDefinition Height="0.125*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label VerticalAlignment="Top">Currency :</Label>
<ComboBox AllowDrop="True" Name="ComboBoxCurrency" VerticalAlignment="Top" SelectionChanged="ComboBoxCurrency_SelectionChanged">
<ComboBoxItem IsSelected="True">EUR</ComboBoxItem>
<ComboBoxItem>USD</ComboBoxItem>
</ComboBox>
</StackPanel>
<DataGrid Grid.Row="1"
Name="DataGridStock"
AutoGenerateColumns="False"
ColumnWidth="*"
x:FieldModifier="public"
IsReadOnly="False"
CanUserAddRows="True"
CanUserDeleteRows="True"
ItemsSource="{Binding StockEntriesCollection}">
<DataGrid.Resources>
<local:CurrencyConverter x:Key="CurrencyConverter"/>
<local:OnStockIconConverter x:Key="OnStockIconConverter"/>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding SoftwarePackage.Name}"></DataGridTextColumn>
<DataGridTextColumn Header="Group" Binding="{Binding SoftwarePackage.Category.Name}"></DataGridTextColumn>
<DataGridTextColumn Header="Sales Price" Binding="{Binding SoftwarePackage.SalesPrice, NotifyOnTargetUpdated=True, Converter={StaticResource CurrencyConverter}}"></DataGridTextColumn>
<DataGridTextColumn Header="Purchase Price" Binding="{Binding SoftwarePackage.PurchasePrice, NotifyOnTargetUpdated=True, Converter={StaticResource CurrencyConverter}}"></DataGridTextColumn>
<DataGridTextColumn Header="Amount" Binding="{Binding Amount}"></DataGridTextColumn>
<DataGridTextColumn Header="On Stock" Binding="{Binding Amount, Converter={StaticResource OnStockIconConverter}}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Margin="3">Add</Button>
<Button Margin="3">Edit</Button>
<Button Margin="3">Delete</Button>
</StackPanel>
</Grid>
</Window>
You should separate your viewmodel and view for you can work with real MVVM. Now your view is at the same time a viewmodel.
To reach the goal add to your entry model a method e.g. NotifyAllChanged to recalculate all properties and call it in your combobox event handler.
public class StockEntryModel : INotifyPropertyChanged
{
...
public void NotifyAllChanged()
{
OnPropertyChanged(string.Empty);
}
}
private void ComboBoxCurrency_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var text = ((sender as ComboBox).SelectedItem as ComboBoxItem).Content as string;
SelectedCurrency.Name = text;
Console.WriteLine("Currency changed to {0}", text);
StockEntriesCollection?.ForEach(entry=>entry.NotifyAllChanged()); // <==
}
How to define custom template for ComboBox in my UWP App, for Dropdown I need a checkbox with label, but when user selects any option I just need to show label in comobox
I tried this but I didn't work:
<ComboBox x:Name="cbCountry"
Header="Country"
Margin="10,5"
HorizontalAlignment="Stretch"
Style="{StaticResource ComboBoxStyle}">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding}"></CheckBox>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.ItemsPanel>
<ItemsPanelTemplate>
<TextBlock Text="{Binding}"/>
</ItemsPanelTemplate>
</ComboBox.ItemsPanel>
<Page.Resources>
<local:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Page.Resources>
<ComboBox x:Name="cbState" DropDownClosed="cbState_DropDownClosed" DropDownOpened="cbState_DropDownOpened" Margin="75,287,0,0" Width="169" ItemContainerStyle="{StaticResource ComboBoxItemStyle1}" Style="{StaticResource ComboBoxStyle1}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox Content="{Binding}"
Visibility="{Binding IsCheckBoxVisible, Mode=TwoWay, Converter={StaticResource BooleanToVisibilityConverter}, UpdateSourceTrigger=PropertyChanged}" >
</CheckBox>
<TextBlock Text="{Binding State_Name}"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
private void cbState_DropDownClosed(object sender, object e)
{
foreach (var item in (sender as ComboBox).Items)
{
(item as State).IsCheckBoxVisible = false;
}
}
private void cbState_DropDownOpened(object sender, object e)
{
foreach(var item in (sender as ComboBox).Items)
{
(item as State).IsCheckBoxVisible = true;
}
}
Converter class
public class BooleanToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
var boolValue = System.Convert.ToBoolean(value);
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
return ((Visibility)value == Visibility.Visible) ? true : false;
;
}
}
Model class. Implement INotifyPropertyChanged to reflect changes in UI(checkbox visibility changes)
public class State:INotifyPropertyChanged
{
public string State_Name { get; set; }
public object State_Id { get; set; }
bool isCheckBoxVisible;
public bool IsCheckBoxVisible
{
get { return isCheckBoxVisible; }
set
{
if (value != isCheckBoxVisible)
{
isCheckBoxVisible = value;
OnPropertyChanged("IsCheckBoxVisible");
}
}
}
public State(string name,object id,bool visibility=false)
{
State_Name = name;
State_Id = id;
IsCheckBoxVisible = false;
}
public event PropertyChangedEventHandler PropertyChanged;
public override string ToString()
{
return State_Name;
}
void OnPropertyChanged(string propertyName)
{
// the new Null-conditional Operators are thread-safe:
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
I have a ComboBox which should have a text when nothing is selected. This seems like a straight forward problem, and has many answers on the net, but unfortunately, it is not working for me. I think, the reason is, that I don't want to show a static text, but rather a bound text.
My minimal not working example looks like this:
public class Model
{
public string Name { get; set; }
public SubModel SelectedItem { get; set; }
public List<SubModel> Items { get; set; }
}
public class SubModel
{
public string Description { get; set; }
}
and the MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var selectedSubModel = new SubModel { Description = "SubModel5" };
var model1 = new Model
{
Name = "Model1",
Items = new List<SubModel>
{
new SubModel { Description = "SubModel1" },
new SubModel { Description = "SubModel2" },
new SubModel { Description = "SubModel3" }
}
};
var model2 = new Model
{
Name = "Model2",
SelectedItem = selectedSubModel,
Items = new List<SubModel>
{
new SubModel { Description = "SubModel4" },
selectedSubModel,
new SubModel { Description = "SubModel6" }
}
};
var model3 = new Model
{
Name = "Model3",
Items = new List<SubModel>
{
new SubModel { Description = "SubModel7" },
new SubModel { Description = "SubModel8" },
new SubModel { Description = "SubModel9" }
}
};
_itemsControl.Items.Add(model1);
_itemsControl.Items.Add(model2);
_itemsControl.Items.Add(model3);
}
}
with xaml:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<ItemsControl x:Name="_itemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="WpfApplication1:Model">
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="Background">
<Setter.Value>
<VisualBrush>
<VisualBrush.Visual>
<TextBlock Text="{Binding Name}"/>
</VisualBrush.Visual>
</VisualBrush>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
This gives the follwing:
But it should look similar to this:
please first of all take in your mind facts provided in the next sentence - you can only select items provided by ComboBox ItemsSource. Thus since the Name property values (Model1, Model2, Model3 etc.) are not in your collection they can't be selected you will see the empty selection Instead. I can suggest you the next solution the combination of data context proxy and wpf behavior.
Xaml code
<Window x:Class="ComboBoxWhenNoAnySelectedHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:comboBoxWhenNoAnySelectedHelpAttempt="clr-namespace:ComboBoxWhenNoAnySelectedHelpAttempt"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl x:Name="_itemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="comboBoxWhenNoAnySelectedHelpAttempt:Model">
<ComboBox x:Name="ComboBox"
SelectedItem="{Binding SelectedItem, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.Resources>
<!--the next object is a proxy that able to provide combo data context each time it requested-->
<comboBoxWhenNoAnySelectedHelpAttempt:FreezableProxyClass x:Key="FreezableProxyClass" ProxiedDataContext="{Binding ElementName=ComboBox, Path=DataContext }"></comboBoxWhenNoAnySelectedHelpAttempt:FreezableProxyClass>
</ComboBox.Resources>
<ComboBox.ItemsSource>
<CompositeCollection>
<!--the next object is a collapsed combo box that can be selected in code-->
<!--keep im mind, since this object is not a SubModel we get the binding expression in output window-->
<ComboBoxItem IsEnabled="False" Visibility="Collapsed" Foreground="Black" Content="{Binding Source={StaticResource FreezableProxyClass},
Path=ProxiedDataContext.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></ComboBoxItem>
<CollectionContainer Collection="{Binding Source={StaticResource FreezableProxyClass},
Path=ProxiedDataContext.Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</CompositeCollection>
</ComboBox.ItemsSource>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<i:Interaction.Behaviors>
<!--next behavior helps to select a zero index (Model.Name collapsed) item from source when selected item is not SubModel-->
<comboBoxWhenNoAnySelectedHelpAttempt:ComboBoxLoadingBehavior/>
</i:Interaction.Behaviors>
</ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
Code Behind with Proxy Code
public class FreezableProxyClass : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new FreezableProxyClass();
}
public static readonly DependencyProperty ProxiedDataContextProperty = DependencyProperty.Register(
"ProxiedDataContext", typeof(object), typeof(FreezableProxyClass), new PropertyMetadata(default(object)));
public object ProxiedDataContext
{
get { return (object)GetValue(ProxiedDataContextProperty); }
set { SetValue(ProxiedDataContextProperty, value); }
}
}
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var selectedSubModel = new SubModel { Description = "SubModel5" };
var model1 = new Model
{
Name = "Model1",
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel1" },
new SubModel { Description = "SubModel2" },
new SubModel { Description = "SubModel3" }
}
};
var model2 = new Model
{
Name = "Model2",
SelectedItem = selectedSubModel,
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel4" },
selectedSubModel,
new SubModel { Description = "SubModel6" }
}
};
var model3 = new Model
{
Name = "Model3",
Items = new ObservableCollection<SubModel>
{
new SubModel { Description = "SubModel7" },
new SubModel { Description = "SubModel8" },
new SubModel { Description = "SubModel9" }
}
};
_itemsControl.Items.Add(model1);
_itemsControl.Items.Add(model2);
_itemsControl.Items.Add(model3);
}
}
public class Model:BaseObservableObject
{
private string _name;
private SubModel _selectedItem;
private ObservableCollection<SubModel> _items;
public string Name
{
get { return _name; }
set
{
_name = value;
OnPropertyChanged();
}
}
public SubModel SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
OnPropertyChanged();
}
}
public ObservableCollection<SubModel> Items
{
get { return _items; }
set
{
_items = value;
OnPropertyChanged();
}
}
}
public class SubModel:BaseObservableObject
{
private string _description;
public string Description
{
get { return _description; }
set
{
_description = value;
OnPropertyChanged();
}
}
}
BaseObservableObject code (simple implementation for INotifyPropertyChanged )
/// <summary>
/// implements the INotifyPropertyChanged (.net 4.5)
/// </summary>
public class BaseObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> raiser)
{
var propName = ((MemberExpression)raiser.Body).Member.Name;
OnPropertyChanged(propName);
}
protected bool Set<T>(ref T field, T value, [CallerMemberName] string name = null)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(name);
return true;
}
return false;
}
}
WPF Behavior Code
public class ComboBoxLoadingBehavior:Behavior<ComboBox>
{
private bool _unLoaded;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += AssociatedObjectOnLoaded;
AssociatedObject.LayoutUpdated += AssociatedObjectOnLayoutUpdated;
AssociatedObject.Unloaded += AssociatedObjectOnUnloaded;
}
private void AssociatedObjectOnUnloaded(object sender, RoutedEventArgs routedEventArgs)
{
_unLoaded = true;
UnsubscribeAll();
}
private void UnsubscribeAll()
{
AssociatedObject.Loaded -= AssociatedObjectOnLoaded;
AssociatedObject.LayoutUpdated -= AssociatedObjectOnLayoutUpdated;
AssociatedObject.Unloaded -= AssociatedObjectOnUnloaded;
}
private void AssociatedObjectOnLayoutUpdated(object sender, EventArgs eventArgs)
{
UpdateSelectionState(sender);
}
private static void UpdateSelectionState(object sender)
{
var combo = sender as ComboBox;
if (combo == null) return;
var selectedItem = combo.SelectedItem as SubModel;
if (selectedItem == null)
{
combo.SelectedIndex = 0;
}
}
private void AssociatedObjectOnLoaded(object sender, RoutedEventArgs routedEventArgs)
{
_unLoaded = false;
UpdateSelectionState(sender);
}
protected override void OnDetaching()
{
base.OnDetaching();
if(_unLoaded) return;
UnsubscribeAll();
}
}
This is a working complete solution for you problem, just copy/past and use it as a starting point for your farther research. I'll glad to help if you will have any problems with the code.
Regards.
I've found 2 possible solutions:
Change ComboBox template
Edit standard combobox template by right button click on combobox in designer and select Edit Template -> Edit a Copy...
After that change ContentPresenter with a custom converter:
XAML
<ContentPresenter ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" IsHitTestVisible="false" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<ContentPresenter.Content>
<MultiBinding Converter="{local:ComboboxEmptyValueConverter}">
<Binding Path="SelectionBoxItem" RelativeSource="{RelativeSource Mode=TemplatedParent}" />
<Binding Mode="OneWay" Path="DataContext" RelativeSource="{RelativeSource Mode=TemplatedParent}" />
</MultiBinding>
</ContentPresenter.Content>
</ContentPresenter>
C#
class ComboboxEmptyValueConverterExtension : MarkupExtension, IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
string stringValue = values[0] as string;
var dataContext = values[1] as Model;
return (stringValue != null && String.IsNullOrEmpty(stringValue)) ? dataContext?.Name : values[0];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { value, null };
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
Set ComboBox to IsEditable & IsReadOnly and change Text
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock x:Name="textBlock" Text="{Binding Description}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="IsEditable" Value="True" />
<Setter Property="IsReadOnly" Value="True" />
<Setter Property="Text" Value="{Binding Name}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
The answer is, to put the visual brush in the resources of the combobox:
<DataTemplate DataType="WpfApplication1:Model">
<ComboBox ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Description}"></TextBlock>
</DataTemplate>
</ComboBox.ItemTemplate>
<ComboBox.Resources>
<VisualBrush x:Key="_myBrush">
<VisualBrush.Visual>
<TextBlock Text="{Binding Name}"/>
</VisualBrush.Visual>
</VisualBrush>
</ComboBox.Resources>
<ComboBox.Style>
<Style TargetType="ComboBox">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem}" Value="{x:Null}">
<Setter Property="Background" Value="{StaticResource _myBrush}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ComboBox.Style>
</ComboBox>
</DataTemplate>
Then, together with the rest of the code, it works like expected.
I have a Button which shows a ContextMenu look like
Here the XAML:
<Window.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
<Style x:Key="MenuItemStyle" TargetType="{x:Type MenuItem}">
<Setter Property="Command" Value="{Binding Data.OnSelected, Source={StaticResource proxy}}" />
</Style>
<Button Name="ButtonMenu_Export"
Click="ButtonMenu_Export_Click"
Visibility="{Binding ButtonExportEnabled,
Converter={StaticResource VisibilityConverter}}">
<StackPanel Orientation="Vertical">
<Image Source="...." />
<TextBlock Width="70" TextAlignment="Center" Text="Export" />
</StackPanel>
<Button.ContextMenu>
<ContextMenu ItemsSource="{Binding ExportMenuItems}">
<ContextMenu.ItemTemplate>
<HierarchicalDataTemplate ItemContainerStyle="{StaticResource MenuItemStyle}">
<ContentPresenter Content="{Binding Text}" RecognizesAccessKey="True" />
<HierarchicalDataTemplate.ItemsSource>
<Binding Path="SubItems" />
</HierarchicalDataTemplate.ItemsSource>
</HierarchicalDataTemplate>
</ContextMenu.ItemTemplate>
</ContextMenu>
</Button.ContextMenu>
</Button>
The menu is created at runtime using this List (as in this article)
public System.Collections.Generic.List<MenuItem> ExportMenuItems
{
get { return _menuService.GetParentMenuItems(); }
}
Now, what I cannot do is bind the items to the OnSelected command of MenuItem class.
The class which defines the menu is:
public class MenuItem
{
private string name;
private string text;
private int menuId;
private ICommand onSelected;
private MenuItem parent;
private ObservableCollection<MenuItem> subItems;
public MenuItem(string name, string text, int MenuId)
{
this.menuId = MenuId;
this.name = name;
this.text = text;
this.subItems = new ObservableCollection<MenuItem>();
}
public string Name { get { return this.name; } }
public string Text { get { return this.text; } }
public MenuItem Parent { get { return this.parent; } set { this.parent = value; } }
public ICommand OnSelected
{
get
{
if (this.onSelected == null)
this.onSelected = new MenuCommand(this.ItemSelected, this.ItemCanBeSelected, menuId);
return this.onSelected;
}
}
public ObservableCollection<MenuItem> SubItems
{
get { return this.subItems; }
}
}
I created a proxy class as in this article to made DataContext visible to HierarchicalDataTemplate content but maybe I misunderstood something:
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
Where I'm wrong?
Change your command binding
Command="{Binding Data.OnSelectedd,Source={StaticResource proxy}, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}"