I am having trouble with what seems like a fairly straightforward task. I have a treeview with nodes, and I would like to set the TreeViewItem 'IsExpanded' property to True for the whole treeview if a specific string property 'SearchTerm' of my View Model is not empty. In other words, if string property is not null, IsExpanded value should be True. I have already done this in codebehind but I prefer to do this in XAML for cleanness.
To describe the code below, I created a converter which will convert a null string to 'False' and non-null to 'True'. In my XAML I call this converter when I attempt to bind the string value from the viewmodel in the TreeView ItemContainerStyle. It appears that the converter is never even fired.
My XAML (simplified):
<UserControl.Resources>
<cv:ExpandNodesIfSearchConverter x:Key="ExpandAll">
</cv:ExpandNodesIfSearchConverter>
</UserControl.Resources>
<TreeView Grid.Row="2" x:Name="myTreeView"
ItemsSource="{Binding Sponsors}"
SelectedItemChanged="TreeView_SelectedItemChanged" >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<!-- if SearchTerm is not null, use converter to set value to true and expand all nodes -->
<Setter Property="IsExpanded" Value="{Binding Path=SearchTerm, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ExpandAll}}" />
</Style>
</TreeView.ItemContainerStyle>
<!-- TreeView data -->
</TreeView>
My View Model:
public class TreeViewVM : INotifyPropertyChanged
{
private string _searchterm;
public string SearchTerm
{
get
{
return _searchterm;
}
set
{
_searchterm = value;
OnPropertyChanged("SearchTerm");
}
}
}
My Converter:
class ExpandNodesIfSearchConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
//if searchterm is not null, return true to expand all items, otherwise return false
string searchterm = value.ToString();
if (string.IsNullOrEmpty(searchterm))
return false;
else
return true;
}
public object ConvertBack(object value, Type targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
I solved my issue by using the ElementName in the Setter tag rather than the viewmodel property. searchTxt being the name of the TextBox which SeachTerm was bound to.
<Setter Property="IsExpanded" Value="{Binding ElementName=searchTxt, Path=Text, Mode=OneWay, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource ExpandAll}}" />
Related
I have converter for applying background color for DataGrid rows based on values in one column. It is working fine and is applying background color "on load". However I decided to dive deeper into WPF and MVVM and try to attach this event to CheckBox, but failed at this point. I am not getting any errors but my CheckBox does not work either. Any suggestions what should be edited? I guess I need to attach Convert event somehow to BacgroundColorBool?
IDToBackgroundConverter.cs:
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace Inspector_FilterTest
{
class IDToBackgroundConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string)
{
if (value.ToString().Trim().StartsWith("7")) return Brushes.DarkSlateBlue;
if (value.ToString().Trim().StartsWith("6")) return Brushes.Peru;
}
// value is not an integer. Do not throw an exception
// in the converter, but return something that is obviously wrong
return Brushes.Firebrick;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
I use it in XAML:
<Window.Resources>
<Inspector_FilterTest:IDToBackgroundConverter x:Key="IDToBackgroundConverter"/>
</Window.Resources>
...
<DataGrid x:Name="DataGrid1" ItemsSource="{Binding MainDataTable}">
<DataGrid.RowStyle>
<Style TargetType="DataGridRow" >
<Setter Property="Background" Value="{Binding YRNRO, Converter={StaticResource IDToBackgroundConverter}}" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
This is working fine and background colors are set (based on starting number in YRNRO) when I am loading my DataGrid. However I would like to be able to control this with CheckBox.
I have created a CheckBox:
ViewModel_Main.cs:
// Binding checkbox background color
private bool _BacgroundColorBool;
public bool BacgroundColorBool
{
get => this._BacgroundColorBool;
set
{
this._BacgroundColorBool = value;
OnPropertyChanged();
// Refresh the DataTable filter expression
EnableRowFiltering();
}
}
XAML:
<CheckBox Style="{StaticResource MyCheckBox}" IsChecked="{Binding BacgroundColorBool}" x:Name="ActiveCustomer_Copy" Content="" HorizontalAlignment="Left" Margin="221,55,0,0" VerticalAlignment="Top"/>
but I don't understand how to connect this all together in order to be able to control "background fill applied"/"background fill not applied" with CheckBox.
EDIT:
Some corrections done to code originally provided by BionicCode (no errors in debugger anymore, I hope this is correct?). In order to get color back to transparent in Datagrid after CheckBox is unchecked pay attention to return Brushes.Transparent;. That is where you can apply "revertBack" logic.
using System;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace Liinos_inspector_FilterTest
{
class IDToBackgroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// The order of the parameters in the 'values' array is equal to the order
// of the input binding as they are defined in the MultiBinding
var isBackgroundEnabled = (bool)values[0];
if (!isBackgroundEnabled)
{
return Brushes.Transparent;
}
if (values[1] is string stringValue)
{
return stringValue.Trim().StartsWith("7")
? Brushes.DarkSlateBlue
: stringValue.Trim().StartsWith("6")
? Brushes.Peru
: Brushes.Firebrick;
}
return Binding.DoNothing;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
One solution, that doesn't require modification of your view model, is to use a MultiBinding with a IMultiValueConverter. A IMultiValueConverter accepts multiple inputs returned from a MultiBinding, to convert them to a single output (and reverse):
First, turn the IValueConverter into a IMultiValueConverter.
It's best practice to throw the exception that describes the cause of the error the best.
The NotImplementedException is primarily intended to notify the developer that he forgot to implement a relevant and referenced method. It's like a 'TODO'. Since you deliberately decided not to implement the ConvertBack member, because you decided not to support the backwards conversion, you should throw a NotSupportedException instead. This notifies the consumer of your type that the method is not supported instead of implying that the author simply forgot to implement it.
class IDToBackgroundConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// The order of the parameters in the 'values' array is equal to the order
// of the input binding as they are defined in the MultiBinding
var isBackgroundEnabled = (bool) values[0];
if (!isBackgroundEnabled)
{
return Brushes.Transparent;
}
if (values[1] is string stringValue)
{
return stringValue.Trim().StartsWith("7")
? Brushes.DarkSlateBlue
: stringValue.Trim().StartsWith("6")
? Brushes.Peru
: Brushes.Firebrick;
}
return Binding.DoNothing;
}
// Throw a NotSupportedException instead of a NotImplementedException
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
=> throw new NotSupportedException();
}
Setup the MultiBinding:
<CheckBox IsChecked="{Binding BackgroundColorBool}" />
<DataGrid>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<IDToBackgroundConverter />
</MultiBinding.Converter>
<Binding Path="BackgroundColorBool" />
<Binding Path="YRNRO" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
</DataGrid>
Because the purpose of the logic is to handle the coloring or visuals of the view, you should not implement this logic or store related data in the view model.
View and view model should never mix!
Whether to use the previous example or to eliminate the BackgroundColorBool property in the view model, depends on the the purpose of the CheckBox. Its name suggests a data related purpose ("ActiveCustomer...") and of course could bind to the view model.
But the name of the source property ("BackgroundColor..."), the CheckBox binds to, suggests a merely view related purpose. In this case the property should not be in the view model. Instead move it as a DependencyProperty to the hosting view or bind the CheckBox directly:
<CheckBox x:Name="ActiveCustomer_Copy" />
<DataGrid>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Setter Property="Background">
<Setter.Value>
<MultiBinding>
<MultiBinding.Converter>
<IDToBackgroundConverter />
</MultiBinding.Converter>
<Binding ElementName="ActiveCustomer_Copy"
Path="IsChecked" />
<Binding Path="YRNRO" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGrid.RowStyle>
</DataGrid>
My model have two independent properties:
enumType DataType
SomeSpec DataSpec
based on value of DataType I would like to interpretate the DataSpec accordantely
<ContentControl DataContext ="{Binding}">
<MultiBinding Converter ="{StaticResource DataContentConverter}">
<Binding Path ="DataType"/>
<Binding Path ="DataSpec"/>
</MultiBinding>
<ContentControl.Style>
<Style TargetType ="ContentControl">
<Style.Triggers>
<DataTrigger Binding ="{Binding DataType}" Value ="Image">
<Setter Property ="Template">
<Setter.Value>
<ControlTemplate>
<Image Source ="{Binding Image, Mode = OneWay}"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
I am using the following converter implementation
public class DataViewConverter: IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] as enumType == null) return null;
var selectedType = (enumType)values[0];
var selectedObject = (SomeSpec)values[1];
switch (selectedType )
{
case enumType.Thumbnail:
case enumType.DisplayImage:
{
return new DataContent
{
DataType = ReducedDataType.Image,
Image = SelectedObject.GetBitmapImage()
};
}
case ...
default:
{
return new DataContent
{
DataType = ReducedDataType.Unknown,
Text = "Content Viewer is not implemented!"
};
}
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] {};
}
}
Implementing this I expect to see an image in my window, but instaed of it I only see a text MyNameSpace.DataContent
What am I missing?
I think that your DataTrigger's binding is being based off the ContentControl's DataContext.
<DataTrigger Binding ="{Binding DataType}" Value ="Image">
So, it is looking at ContentControl.DataContext.DataType.
I think what you want is for it to look at the DataType property of the ContentControl's Content. Try changing the DataTrigger to this:
<DataTrigger Binding ="{Binding Content.DataType, RelativeSource={RelativeSource Self}}" Value ="Image">
So now it is looking at ContentControl.Content.DataType. The RelativeSource part directs the binding to the ContentControl instead of ContentControl.DataContext.
I have a field in my UI that can either take a date or a string.
I would like to switch on the type of control displayed depending on the data that is loaded in there or being input by the user.
In other words:
DatePicker if the user starts inputting some numbers (or data loaded is a date)
TextBox is a string input (or string loaded)
Can't find yet how to switch. Happy if you had some tip. Thank you!
You would need to use templates depending on a type. To do this you would need either have 2 properties , one with type of a property and another with the actial object (both notifying INotifyPropertyChanged ).
public object YourProperty
{
get
{
return yourProperty;
}
set
{
yourProperty = value;
OnPropertyChanged();
DateTime date;
if(yourProperty is String && DateTime.TryParse((string) yourProperty, out date))
{
YourProperty = date;
}
}
}
private object yourProperty = string.Empty;
//public Type YourPropertyType { get; set; }
You also can create a converter which will return the type of a property, so you can get rid of additional property (commented out above):
public class TypeOfConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return (value == null) ? null : value.GetType();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
And finally bind a ContentControl to your property and select a template using the converter above:
<ContentControl Content="{Binding YourProperty, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ContentControl.Resources>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=YourProperty,Converter={flowMathTest:TypeOfConverter}}" Value="{x:Type system:DateTime}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<DatePicker SelectedDate="{Binding Content, RelativeSource={RelativeSource AncestorType=ContentControl}, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Path=YourProperty,Converter={flowMathTest:TypeOfConverter}}" Value="{x:Type system:String}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding Content, RelativeSource={RelativeSource AncestorType=ContentControl}, UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Resources>
</ContentControl>
Thant should do it.
EDIT: I did not read the second part so the switching between controls should happen as user writes.
What you can do in this case is to change bindings to Mode=TwoWay and UpdateSourceTrigger=PropertyChanged and handle detection of type in code.
By default set YourProperty to string.Empty. Then on every change check if the text entered is already date using DateTime.Parse. If it is, set YourProperty to this date.
So I updated the code above to reflect these changes.
I want to bind IsEnabled of Button in WPF as follows:
WPF Code:
<Button Content="TestButton" IsEnabled="{Binding ??}" />
C# code:
private MyObjectClass _Checked;
public MyObjectClass Checked
{
get { return _Checked; }
set
{
_Checked = value;
RaisePropertyChanged("Checked");
}
}
In WPF code above, I want the button to be enabled only when Checked object is not null. I know one way is to have a bool property in C# code which tells me whether the Checked object is null or not, and then bind it to IsEnabled property. I want to know if there is a way I can bind IsEnabled to Checked object directly?
Use DataTrigger and check for {x:Null} value of binding:
<Button Content="TestButton">
<Button.Style>
<Style TargetType="Button">
<Style.Triggers>
<DataTrigger Binding="{Binding Checked}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
Also, you can use IValueConverter which will return false if value is null otherwise true.
public class ObjectToBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
CultureInfo culture)
{
bool returnValue = true;
if (value == DependencyProperty.UnsetValue || value == null)
{
returnValue = false;
}
return returnValue;
}
public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture)
{
return Binding.DoNothing;
}
}
and bind in XAML:
<Button Content="TestButton"
IsEnabled="{Binding Checked,
Converter={StaticResource ObjectToBoolConverter}}" />
Ofcourse you need to declare instance of converter in your XAML for this.
You can use a converter to convert an object into a bool. Look into IValueConverter.
public class IsNotNullToBoolConverter: IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException("Two-way binding not supported by IsNotNullToBoolConverter");
}
}
And your xaml would look like this:
<Window.Resources>
<local:IsNotNullToBoolConverter x:Key="IsNotNull" />
</Window.Resources>
...
<Button IsEnabled="{Binding Converter={StaticResource IsNotNull}}" />
I know this is an old issue but you can do this without an extra code. Just add the "TargetNullValue=false" to the binding.
IsEnabled="{Binding SomeProperty, TargetNullValue=false}"
One of my views consists of 5 UserControls that each display data about a certain object. Let's say for example that the view displays the cows our company has, and on the screen cows 1 through 5 are displayed (each in their own UserControl).
What I want to do (but not sure is possible) is to bind the status of a cow to the style used in its respective UserControl. So we have a property status that could be ok, hungry, dead for example. In case the cow is ok I want to display a 'normal' style, if it's hungry I want the background to be red and if it's dead I want the text to be black and the fontsize increased.
I've added a simplified version of what I'm trying to achieve. My knowledge of WPF styles/resource dictionaries is still somewhat limited though.
What I basically want in code
A ViewModel with a Status property
class CowInfoViewModel : Screen
{
public string Name { get; set; }
public string Status { get; set; } //"ok", "hungry", "dead"
}
A View that retrieves a style or resourcedictionary
<UserControl x:Class="WpfModifyDifferentView.Views.CowInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- A reference to a ResourceDictionary with styles, that is bound to the 'Status' property -->
<StackPanel>
<TextBlock x:Name="Name" Text="Cow Name"/>
<TextBlock x:Name="Status" Text="Ok" />
</StackPanel>
</UserControl>
EDIT - Solution:
I did the following using Vale's answer:
In the xaml (reference to the converter):
<UserControl.Resources>
<Converters:CowStyleConverter x:Key="styleConverter" />
</UserControl.Resources>
In the xaml (elements):
<TextBlock x:Name="Name" Text="Cow Name" Style="{Binding Path=Style, ConverterParameter='TextBlockCowName', Converter={StaticResource styleConverter}}" />
The converter (note I left out the checks):
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var status = value.ToString();
var styleName = parameter.ToString();
_resourceDictionary.Source = new System.Uri(string.Format("pack://application:,,,/Resources/ScreenI2Style{0}.xaml", status));
return _resourceDictionary[styleName];
}
Then I created multiple ResourceDictionaries with styles such as:
<Style x:Key="TextBlockCowName" TargetType="TextBlock">
<Setter Property="Foreground" Value="{StaticResource SomeBrush}" />
</Style>
You can bind UserControl Style property to Status and use a converter.
<UserControl x:Class="WpfModifyDifferentView.Views.CowInfoView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfModifyDifferentView"
Style="{Binding Path=Status, Converter={StaticResource myConverter}}">
<UserControl.Resources>
<local:MyConverter x:Key="myConverter" />
</UserControl.Resources>
I assume that your converter is in WpfModifyDifferentView directly.
Converter will look like this:
public class MyConverter : IValueConverter {
private ResourceDictionary dictionary;
public MyConverter() {
if (dictionary == null) {
dictionary = new ResourceDictionary();
dictionary.Source = new Uri("pack://application:,,,/WpfModifyDifferentView;Component/Resources/Styles.xaml");
}
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
switch (value.ToString()) {
case "ok":
return dictionary["myKeyForOkStyle"] as Style;
case "hungry":
return dictionary["myKeyForHungryStyle"] as Style;
case "dead":
return dictionary["myKeyForDeadStyle"] as Style;
default:
return null;
}
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
You need to specify the correct URI of course.