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.
Related
I have a DataGrid displaying a list of maps with some details. I'm trying to change the foreground colour of the map which is currently active but I'm struggling. I've written the following converter to check if the DataGrid item's value matches the current map's name, but I'm not sure how to pass the name of the current map from XAML.
Converter:
public class StringToBrushConverter : IValueConverter
{
public string CurrentMapName { get; set; }
public Color CurrentMapColor { get; set; }
public Color OtherMapColor { get; set; }
string _value = "";
public StringToBrushConverter(string currentMapName, Color currentMapColor, Color otherMapColor)
{
CurrentMapName = currentMapName;
CurrentMapColor = currentMapColor;
OtherMapColor = otherMapColor;
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
_value = value as string;
return (string)value == CurrentMapName ? new SolidColorBrush(CurrentMapColor) : new SolidColorBrush(OtherMapColor);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return _value; //TODO: Will this work?
}
}
public sealed class RaceMapColorConverter : StringToBrushConverter
{
public RaceMapColorConverter() : base("", new Color { R = 255, G = 0, B = 0 }, new Color { R = 255, G = 255, B = 255 }) { }
}
XAML:
<DataGrid x:Name="MapHistory"
AutoGenerateColumns="False"
IsReadOnly="True"
HeadersVisibility="Column"
GridLinesVisibility="None"
CanUserSortColumns="False"
Style="{StaticResource MapTimerDataGrid}"
CellStyle="{StaticResource MapTimerControlCell}"
ItemsSource="{Binding Plan.Plan}">
<DataGrid.Resources>
<!-- The Binding in CurrentMapName is not valid. It says it can only be set on a dependency property but I'm not sure how to do that -->
<conv:RaceMapColorConverter x:Key="MapColorConverter" CurrentMapName="{Binding CurrentMap.MapName}" CurrentMapColor="Red" OtherMapColor="White" />
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Map" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Content="{Binding Map.MapName}" Foreground="{Binding Map.MapName, Converter={StaticResource MapColorConverter}}" Padding="0" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<!-- Other columns removed to shorten -->
</DataGrid.Columns>
</DataGrid>
I'm also not sure how to implement 'ConvertBack()`. Will storing the original value in the converter work? or could a new instance be created for the same control? Am I even going about this the right way? Thought it's also worth mentioning that I'm using Caliburn.Micro just in case it has any tricks that may help. Any help would be appreciated.
As a general rule-of-thumb you should use converters when the data conversion needs to be two-way (i.e. view-model to view and back again) and DataTriggers when it's one-way only. That answers your question about ConvertBack (which in the example you've provided never gets called anyway, in which case you can just return Binding.DoNothing).
In this particular case, however, a converter is warranted because you need to compare two bindings. Instead of writing an application-aware converter for this one very specific task, I would instead opt for a more generic MultiConverter that simply compares two values that you provide it:
public class EqualityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return (values.Length==2) && Object.Equals(values[0], values[1]);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return new object[] { Binding.DoNothing, Binding.DoNothing };
}
}
Then back in your application you pass both of your bindings into a MultiBinding of this type and use a DataTrigger to change the map color when the result of the comparison is true:
<Label Content="{Binding Map.MapName}">
<Label.Style>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type Label}}">
<Setter Property="Foreground" Value="Red" /> <!-- normal map color -->
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding Converter="{StaticResource EqualityConverter}" Mode="OneWay">
<Binding Path="Map.MapName" />
<Binding Path="CurrentMapName" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Foreground" Value="White" /> <!-- other map color -->
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
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}}" />
I request data over the internet and generally bind it in XAML.
Now I have difficulty accessing that received data, manipulate it and display it in user control.
public partial class FullQuestionUserControl : UserControl
{
public FullQuestionUserControl()
{
InitializeComponent();
}
}
My model Questions contains fields such as id, authorFullName, text, containsImage.
This is how I bind:
<TextBlock Style="{StaticResource TextSmall}" TextWrapping="Wrap"
Text="{Binding SelectedQuestion.authorFullName}" />
I need to check containsImage. If true, then format a new string using id, and display it.
I know how to display the image:
var bi = new BitmapImage(new Uri(url));
this.QuestionImage.Source = bi;
All I need is to get the Question in user control code.
How do I get the data in user control code?
This style will set the Image Source property when containsImage is true:
<Image>
<Image.Resources>
<stackoverflow:IdToImageSourceConverter x:Key="IdToImageSourceConverter"/>
</Image.Resources>
<Image.Style>
<Style TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedQuestion.containsImage}" Value="True">
<Setter Property="Source" Value="{Binding SelectedQuestion.id, Converter={StaticResource IdToImageSourceConverter}}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
This converter will take the id property, format to the URL and return a BitmapImage for the image source:
class IdToImageSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var idValue = value.ToString();
var url = string.Format("http://myurl.com/{0}", idValue);
return new BitmapImage(new Uri(url));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I have a multiBinding which works great,
I want to be able to sort a certain column, it displays w:width, h:heightAs far as I understand I need to build a custom IComparer class which will to the comparison.
Here is my XAML
<igWPF:UnboundField Label="Output
Width/Height" Width="auto">
<igWPF:Field.Settings>
<igWPF:FieldSettings SortComparer="{StaticResource SortWidthHeightComparer }">
<igWPF:FieldSettings.CellValuePresenterStyle>
<Style TargetType="{x:Type igWPF:CellValuePresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igWPF:CellValuePresenter}" >
<TextBlock Margin="3">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource settingsBdsToStringConverter}">
<Binding Path="DataItem.Key"/>
<Binding Path="DataContext.SelectedPipeMode" RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type igWPF:XamDataGrid}}"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</igWPF:FieldSettings.CellValuePresenterStyle>
</igWPF:FieldSettings>
</igWPF:Field.Settings>
</igWPF:UnboundField>
Here is my multibinding converter
class SettingsOutputResToStringConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[1] is Mode && values[0] is ConfigurationKey)
{
var pMode = (Mode)values[1];
var key = values[0] as ConfigurationKey;
var res = key.GetOutput(pMode);
return String.Format("W: {0}, H: {1}", res.Width, res.Height);
}
return String.Empty;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return null;
}
}
However my problem is how to I pass the multibind result into the Comparer class
class SortWidthHeightComparer : IComparer
{
public int Compare(object x, object y)
{
return 1;
}
}
object x and object y , are always null
Gilad,
I found this post:
http://www.infragistics.com/community/forums/t/17878.aspx
It seems like a simple solution.
I'm going to try the same approach for filtering records.
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.