I have a StackPanel that I would like to hide when there isn't a selected item from a ListView in the same window. Currently, when I open the window, there is no selected item and the StackPanel is hidden, but when I do select something from the ListView, no change occurs.
I am binding the SelectedItem in the ListView like:
<ListView
MinHeight="0"
MaxHeight="500"
Margin="10,10,10,0"
Background="#e7f5f4"
BorderThickness="0"
ItemsSource="{Binding Issues}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding SelectedIssue}"
SelectionMode="Single">
Where "SelectedIssue" is a custom class property in my ViewModel (my entire window has the same DataContext). I am currently binding the Visibility property of my StackPanel as:
<StackPanel
Grid.Column="1"
Margin="13,0,0,5"
VerticalAlignment="Bottom"
Background="#ebf7f6"
Orientation="Horizontal"
Visibility="{Binding SelectedIssue,
Converter={StaticResource NullToVisibilityConverter},
UpdateSourceTrigger=PropertyChanged}">
And my converter is:
public class NullToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value == null ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
What am I missing?
EDIT: Here is my getter/setter
private Issue _selectedIssue;
public Issue SelectedIssue
{
get { return _selectedIssue; }
set { Set(ref _selectedIssue, value); }
}
public void RaisePropertyChanged([CallerMemberName]string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public bool Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
{
if (Equals(storage, value))
return false;
storage = value;
RaisePropertyChanged(propertyName);
return true;
}
apparently the problem is that you have not implemented INotifyPropertyChanged or you not raise (in the SelectedIssue property setter) the PropertyChanged Event.
but you can do simpler, binding the StackPanel directly to ListView.SelectedItem:
<ListView x:Name="listView"
MinHeight="0"
MaxHeight="500"
Margin="10,10,10,0"
Background="#e7f5f4"
BorderThickness="0"
ItemsSource="{Binding Issues}"
ScrollViewer.HorizontalScrollBarVisibility="Hidden"
SelectedItem="{Binding SelectedIssue}"
SelectionMode="Single">
<StackPanel
Grid.Column="1"
Margin="13,0,0,5"
VerticalAlignment="Bottom"
Background="#ebf7f6"
Orientation="Horizontal"
Visibility="{Binding SelectedItem, ElementName=listView
Converter={StaticResource NullToVisibilityConverter}" >
Related
i have the following ItemsControl defined, i want to bind the Content of a Button to a object Property path, which will be provided as a string through a method. The Items are generic. How can i achieve this ? because i can't set DisplayMemberPath and bind it to the ItemTemplate. (i cant't set both DisplayMemberPath and ItemTemplate)
<ScrollViewer Grid.Row="2"
Grid.Column="1"
Margin="5 3"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
Style="{DynamicResource MaterialDesignFlatButton}"
Margin="2"
CommandParameter="{Binding}">
<Button.Content>
<TextBlock Text="{Binding}" />
</Button.Content>
</Button>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
ViewModel:
public class SelectionViewModel<T> : DialogViewModelBase, IDialogViewModel
{
// Fields
private string _title;
private bool _showTitleSeparator;
private string _displayMemberPath;
private IEnumerable<T> _items;
// Properties
public string Title
{
get { return _title; }
set { _title = value; Notify(); }
}
public IEnumerable<T> Items
{
get { return _items; }
set { _items = value; Notify(); }
}
// Member Path to bind to
public string DisplayMemberPath
{
get { return _displayMemberPath; }
set { _displayMemberPath = value; Notify(); }
}
}
if i remove the ItemTemplate and simply bind the DisplayMemberPath of the ItemsControl to the Property in my ViewModel it works fine.
I was able to do this using a converter and reflection.
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (values.Contains(DependencyProperty.UnsetValue))
{ return null; }
object source = values[0];
string path = (string)values[1];
Type sourceType = source.GetType();
System.Reflection.PropertyInfo propertyInfo = sourceType.GetProperty(path);
return propertyInfo.GetValue(source, null);
}
Binding has to be set with Path to the property name of the class whose list is bound to the ItemsSource.
if your class looks like:
Class MyItem{
String Prop1 { get; }
}
Then binding will be
<TextBlock Text="{Binding Path=Prop1}" />
I have a combobox with StaticResource called Currencies, Currencies is an ObservableCollection of some class
in this class i have a parameter called CurrencyID.
I have another class called Item which have a parameter called CurrencyID.
i want to set the Item.CurrencyID from the combobox, for that i use ValueSelected and ValueSelectedPath with a converter from comboboxitem to the CurrencyID.
and it works.
next i want to select the comboboxitem from the CurrencyID of the Item class.
But it always show unselected comboboxitem.
Here's the relevant code:
XAML:
<Window.Resources>
<converters:sp_GetCurrencies_ResultToID x:Key="sp_GetCurrencies_ResultToIDConverter"/>
<CollectionViewSource x:Key ="Currencies" Source="{Binding Currencies}"/>
</Window.Resources>
<ComboBox x:Name="cmbCurrencies" ItemsSource="{Binding Source={StaticResource Currencies} }" SelectedValuePath="{Binding CurrencyID, Mode=TwoWay}" SelectedValue="{Binding Item.CurrencyID, Converter={StaticResource sp_GetCurrencies_ResultToIDConverter}}" DisplayMemberPath="CurrencyName" Width="150" Height="30" VerticalContentAlignment="Center" FlowDirection="RightToLeft" VerticalAlignment="Top" HorizontalAlignment="Right" Margin="5"/>
Converter:
public class sp_GetCurrencies_ResultToID : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
using (PricingManagerEntities db = new PricingManagerEntities())
return db.sp_GetCurrencies((long)value,null).FirstOrDefault();
else
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null)
{
return (value as sp_GetCurrencies_Result).CurrencyID;
}
else return null;
}
}
Inside the code i set Item.CurrencyID = 1.
I'm getting also this error, in which i think it's related:
BindingExpression path error: 'CurrencyID' property not found on 'object' ''ItemsViewModel' (HashCode=45202739)'. BindingExpression:Path=CurrencyID; DataItem='ItemsViewModel' (HashCode=45202739); target element is 'ComboBox' (Name='cmbCurrencies'); target property is 'SelectedValuePath' (type 'String')
The program '[12436] PricingManager.vshost.exe' has exited with code 0 (0x0).
Thank you for your help.
I have combobox with binded dictionary
Dictionary:
public Dictionary<string,DateTime> TimeItems { get; set; }
<ComboBox Grid.Column="3"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedIndex="0"
SelectedItem="{Binding SelectedItem}">
How can i bind to public DateTime SelectedItem { get; set; } value of TimeItems
You can use converter to bind the the value of Dictionary to SelectedItem.
public class DictionaryValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ((KeyValuePair<string, DateTime>)value).Value;
}
}
XAML
<ComboBox Grid.Column="3" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedItem="{Binding SelectedItem, Converter={StaticResource DictionaryValueConverter}}" />
You can set SelectedValuePath to "Value" (since each item is a KeyValuePair) and then bind SelectedValue property to your property:
<ComboBox Grid.Column="3"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
ItemsSource="{Binding TimeItems}"
SelectedIndex="0"
SelectedValuePath="Value"
SelectedValue="{Binding SelectedItem}"/>
If you are only interested in displaying and retrieving the values of the Dcitionary then you can use the below option
ViewModel
class ExpenseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName)
{
var pc = PropertyChanged;
if (pc != null)
{
pc(this, new PropertyChangedEventArgs(propName));
}
}
private void Populate()
{
_mySource.Add("Test 1", DateTime.Now);
_mySource.Add("Test 2", DateTime.Now.AddDays(1));
_mySource.Add("Test 3", DateTime.Now.AddDays(2));
_mySource.Add("Test 4", DateTime.Now.AddDays(3));
NotifyPropertyChanged("MySource");
}
private Dictionary<string, DateTime> _mySource;
public Dictionary<string, DateTime> MySource
{
get { return _mySource; }
}
public ExpenseViewModel()
{
_mySource = new Dictionary<string, DateTime>();
Populate();
}
public DateTime SelectedDate
{
get;
set;
}
}
View.xaml
<StackPanel HorizontalAlignment="Left">
<StackPanel.Resources>
<local:Converters x:Key="Converter"/>
</StackPanel.Resources>
<ComboBox x:Name="cmbBox" Width="200" Margin="10,0,20,0" ItemsSource="{Binding MySource, Converter={StaticResource Converter}}"
SelectedItem="{Binding SelectedDate, Mode=TwoWay}" Height="20">
</ComboBox>
</StackPanel>
where local is the xmlns of the project containing the converter
Converter
public class Converters : IValueConverter
{
private static readonly PropertyInfo InheritanceContextProp = typeof(DependencyObject).GetProperty("InheritanceContext", BindingFlags.NonPublic | BindingFlags.Instance);
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var dic = value as Dictionary<string, DateTime>;
return dic.Values;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
In view behind code I have just defined the datacontext
Try this:
foreach (KeyValuePair<string,DateTime> values in TimeItems){
comboBox.Items.Add(values.Value);
}
I have created a user control that has a Label and a ComboBox. It is used like this:
<cc:LabeledComboBox
HeaderLabelContent="Months"
ItemsSource="{Binding AllMonths}"
SelectedValue="{Binding SelectedMonth}"/>
And here is what the UserControl XAML looks like:
<UserControl x:Class="CustomControls.LabeledComboBox" ...>
<UserControl.Resources>
<converters:MonthEnumToTextConverter x:Key="MonthEnumToTextConverter" />
</UserControl.Resources>
<DockPanel>
<Label x:Name="LblValue" DockPanel.Dock="Top"/>
<ComboBox x:Name="LstItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<!-- TODO: Fix so that the Converter can be set from outside -->
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</UserControl>
In the comment above you can see my problem. The control is generic (the ComboBox can contain pretty much anything) but on the Binding inside the DataTemplate I have specified a Converter that is very specific.
How can I specify the Converter from outside the UserControl?
I'm hoping for some kind of solution using a dependency property like this:
<cc:LabeledComboBox
...
ItemConverter="{StaticResource MonthEnumToTextConverter}"/>
You may have an internal binding converter that delegates its Convert and ConvertBack calls to one set is settable as dependency property:
<UserControl ...>
<UserControl.Resources>
<local:InternalItemConverter x:Key="InternalItemConverter"/>
</UserControl.Resources>
<DockPanel>
...
<ComboBox>
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding
Converter={StaticResource InternalItemConverter}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</DockPanel>
</UserControl>
The internal converter could look like this:
class InternalItemConverter : IValueConverter
{
public LabeledComboBox LabeledComboBox { get; set; }
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (LabeledComboBox != null && LabeledComboBox.ItemConverter != null)
{
value = LabeledComboBox.ItemConverter.Convert(
value, targetType, parameter, culture);
}
return value;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
if (LabeledComboBox != null && LabeledComboBox.ItemConverter != null)
{
value = LabeledComboBox.ItemConverter.ConvertBack(
value, targetType, parameter, culture);
}
return value;
}
}
And finally the dependency property code like this:
public partial class LabeledComboBox : UserControl
{
private static readonly DependencyProperty ItemConverterProperty =
DependencyProperty.Register(
"ItemConverter", typeof(IValueConverter), typeof(LabeledComboBox));
public IValueConverter ItemConverter
{
get { return (IValueConverter)GetValue(ItemConverterProperty); }
set { SetValue(ItemConverterProperty, value); }
}
public LabeledComboBox()
{
InitializeComponent();
var converter = (InternalItemConverter)Resources["InternalItemConverter"];
converter.LabeledComboBox = this;
}
}
You can create multiple datatemplates for the the combobox items and then you can control what and how you want to display your comboxitem like below
<DataTemplate DataType="{x:Type vm:MonthDataTypeViewModel}" >
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
<DataTemplate DataType={x:Type vm:OtherViewModel}">
<TextBlock Text="{Binding}"/>
</DataTemplate>
If you do not have multiple viewmodels then you can use a template selector to select different data templates based on some property in your viewmodel.
OP here. Presenting the solution that I'll use until I find something better.
I don't specify only the Converter, but the whole DataTemplate:
<cc:LabeledComboBox>
<cc:LabeledComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={StaticResource MonthEnumToTextConverter}}"/>
</DataTemplate>
</cc:LabeledComboBox.ItemTemplate>
</cc:LabeledComboBox>
And here's the ItemTemplate dependency property:
public partial class LabeledComboBox : UserControl
{
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
"ItemTemplate",
typeof(DataTemplate),
typeof(LabeledComboBox),
new PropertyMetadata(default(DataTemplate), ItemTemplateChanged));
private static void ItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var labeledComboBox = (LabeledComboBox)d;
labeledComboBox.LstItems.ItemTemplate = (DataTemplate)e.NewValue;
}
public DataTemplate ItemTemplate
{
get { return (DataTemplate)GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
// ...
}
EDIT: reworked to not use my personal example to avoid confusion ...
On your user control code behind you could define your dependency property.
I don't know what type your converters derive from so change 'myConverterType' to the type of converters you use.
public bool ItemConverter
{
get { return (myConverterType)GetValue(IntemConverterProperty); }
set { SetValue(ItemConverterProperty, value); }
}
public static readonly DependencyProperty ItemConverterProperty =
DependencyProperty.Register("ItemConverter", typeof(myConverterType),
typeof(LabeledComboBox), null);
In XAML you should then just be able to set the converter property as per your example. In my example it is used like this:
<cc:LabeledComboBox ItemConverter="{StaticResource theSpecificConverter}"/>
Then use this property, on your user control xaml, like this:
<ComboBox x:Name="LstItems">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Converter={Binding ItemConverter, ElementName=UserControl}}"/>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I have some problem in wpf application.
In XAML:
<Expander Header="SomeHeader" Style="{StaticResource ExpanderStyle}" IsExpanded="{Binding ElementName=Errors, Converter={StaticResource visibilityConverter}, Path=IsExpanded}" >
<RichTextBox ScrollViewer.VerticalScrollBarVisibility="Visible" Style="{StaticResource RichTextBoxStyle}" Foreground="Red" IsReadOnly="True">
<FlowDocument>
<Paragraph>
<ItemsControl ItemsSource="{Binding Path=Errors}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Style="{StaticResource ErrorTextBlockStyle}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Paragraph>
</FlowDocument>
</RichTextBox>
</Expander>
In my ViewModelClass:
private List<string> errors;
public List<string> Errors
{
get { return errors; }
set
{
errors = value;
OnPropertyChanged("Errors");
}
}
In constructor:
public MainWindowViewModel()
{
if (IsInDesignMode) return;
Errors = new List<string>();
}
In test method:
private void TestExcute()
{
Errors = "Some error";
}
In this situation error message not displayed in wpf window. But if I change code in constructor to next:
public MainWindowViewModel()
{
if (IsInDesignMode) return;
Errors = new List<string>{"errorMessage1", "errorMessage2"};
}
Displayed:
errorMessage1
errorMessage2
What is the reason ?
I have new question. In this wpf application I also used Expander control. How create auto expand open, then Errors.count > 0?
I create converter :
public class VisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
LoadFile loadFile = (LoadFile)value;
if (loadingFile.ExcelErrors.Count > 0)
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
LoadFile is a class where declared Errors property.
I think you made an error in your TestExcute while writing question did you mean to write Errors.Add("some error")?
If so then your ItemsControl wont react to change because there is no change on property Errors - setter is not invoked.
Change your List<string> to ObservableCollection<string> this class notifies that its content has change and UI will react to that.