Is it possible to use a wild card or call a method to work out if a DataTrigger should be applied?
I currently have my DataList bound to an IEnumerable that contains file names and I want the file names to be greyed out if there files extension starts with "old"
My non-working dream xaml markup looks something like this:
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding}" Value="*.old*">
<Setter TargetName="FileName" Property="Foreground" Value="Gray"/>
</DataTrigger>
</DataTemplate.Triggers>
The only workable solution I've been able to come up with is to insert a new view model property that contains this logic, but I would like to avoid changing the view model if possible.
The answer to both questions is yes....in a roundabout way
If you use a Binding Converter you can pass a parameter to it and have it return a boolean, that would be an effective way to do what you describe.
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=., Converter={StaticResource myFileExtensionConverter}, ConverterParameter=old}" Value="True">
<Setter TargetName="FileName" Property="Foreground" Value="Gray"/>
</DataTrigger>
</DataTemplate.Triggers>
where the converter would look something like this
public class MyFileExtensionConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
Boolean returnValue = false;
String fileExtension = parameter as String;
String fileName = value as String;
if (String.IsNullOrEmpty(fileName)) { }
else if (String.IsNullOrEmpty(fileExtension)) { }
else if (String.Compare(Path.GetExtension(fileName), fileExtension, StringComparison.OrdinalIgnoreCase) == 0) {
returnValue = true;
}
return returnValue;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
return value;
}
}
basically when the file extension matches you get a "true" which will fire the trigger.
Related
I have a table, and when the user would like to mark a row. he can click on button and mark it.
so I wrote a converter for the property, that if its true it return color(yellow)
and if false it return white,
however it Deletes the default style when user select a row in the table.
I was thinking of using Multibuilding once for mark, and one for selected.
however I am not understand what the syntax should be in WPF.
attaching the code I wrote,
will appreciate a code examples.
WPF:
<Style TargetType="syncfusion:GridCell" x:Key="ComCell">
<Setter Property="Foreground" Value="{Binding COMPort , Converter={StaticResource CVconverters } }" />
<Setter Property="Background" Value="{Binding isBookMarked, Converter={StaticResource BookMarkConverter}}"></Setter>
</Style>
C#:
public class BookMarkConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
string valStr = value.ToString();
if (valStr == "True")
{
return Brushes.Yellow;
}
else
{
}
return Brushes.White;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Instead of returning Brushes.White from the converter, you could return Binding.DoNothing from the converter if you simply don't want to change the background.
You could also base your Style on the default one:
<Style TargetType="syncfusion:GridCell" x:Key="ComCell"
BasedOn="{StaticResource {x:Type syncfusion:GridCell}}">
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();
}
}
If IsEnabled property is true, I need to set the Style attribute otherwise it shouldn't be set. In the examples I've seen so far, style properties are set but not the style attribute itself. Below code is not working using Triggers.
<TabItem.Style>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding IsEnabled}" Value="True">
<Setter Property="Style" Value="DotcomTabItemStyle" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
Since you're setting the Trigger through Style, changing the Style would also remove the Trigger... Not really sure if that'll work out :P
Anyway, you're making a mistake on your Setter (setting the resource name directly, not through a static or dynamic resource reference). And you don't need a DataTrigger. It should be:
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Style" Value="{StaticResource DotcomTabItemStyle}" />
</Trigger>
But as I said, this won't probably work as intended, since you're trying to modify the Style property from within the current Style...
One way or another, you'll end up adding different Setters for each property, probably either modifying the DotcomTabItemStyle Style you already have, or creating a new one (based on that one, maybe).
EDIT - Or you could use a Converter and bind the Style property to the IsEnabled property.
I've created a reusable Converter for all this kind of situations:
public class ConditionalSetterConverter : IValueConverter
{
public bool Inverse { get; set; }
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
bool flag = (bool)value;
if (flag ^ Inverse)
return parameter;
else
return Binding.DoNothing;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
You use it like this:
<Window>
<Window.Resources>
<converters:ConditionalSetterConverter x:Key="InverseConditionalSetterConverter"
Inverse="True" />
<Style x:Key="DotcomTabItemStyle" TargetType="TabItem">...</Style>
</Window.Resources>
<TabControl>
<TabItem Style="{Binding IsEnabled,
RelativeSource={RelativeSource Mode=Self},
Converter={StaticResource InverseConditionalSetterConverter},
ConverterParameter={StaticResource DotcomTabItemStyle}}" />
</TabControl>
</Window>
EDIT 2 - OR... You could use a Style selector. ItemsControls like TabControl have a property called ItemContainerStyleSelector, of type StyleSelector.
You'd have to create your own class, inheriting StyleSelector, and override the SelectStyle function to include your custom logic there.
Something like this:
public class DotcomTabItemStyleEnabledSelector : StyleSelector
{
private Style style = null;
public override System.Windows.Style SelectStyle(object item, System.Windows.DependencyObject container)
{
var tabItem = container as TabItem;
if (tabItem != null && tabItem.IsEnabled)
{
if (style == null)
style = textBox.TryFindResource("DotcomTabItemStyle") as Style;
return style;
}
return null;
}
}
I've never used Style selectors, so I'm not really sure if this would work out of the box, but at least you get the idea.
I've got an editable datagrid on a WPF form inside an MVVM application.
There are two possible actions a user can take on this page that cause some data inside one of the rows to change. One of the editable fields - Requested - can cause another property to change, and there's a style trigger dependent on its value:
public bool OverRequested
{
get
{
if(this.Requested > this.Volume)
{
return true;
}
return false;
}
}
And in the XAML:
<DataGridTextColumn Header="Requested" Binding="{Binding Requested}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Style.Triggers>
<DataTrigger Binding="{Binding OverRequested}" Value="true">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="ToolTip" Value="The requested volume is greater than the available volume" />
</DataTrigger>
</Style.Triggers>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
The other is a button that updates the data item behind the row and changes a value on the item.
Although the datagrid itself is responsive to changes - if you add or remove rows from the underlying ObservableCollection it updates accordingly in the UI - changes to these properties on the items underneath the rows do not update.
I understand that this is by design, in the sense that changes to items inside an observableCollection do not bubble up, and have to be watched for specifically, but I'm struggling to find out how to achieve what I want.
I've tried - as suggested elsewhere - overriding the event handler for CollectionChanged on the ObservableCollection to add a notification alert to the items inside:
private void ObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
(item as INotifyPropertyChanged).PropertyChanged += new PropertyChangedEventHandler(item_PropertyChanged);
}
}
}
But items inside do not implement the INotifyPropertyChanged interface - they're Entity Framework objects and not ViewModels. So the cast fails.
Is there a way I can implement this? With only two properties to watch, I'd happily do it manually - but even calling OnPropertyChanged("") to update all the properties in the ViewModel doesn't cause those inside the datagrid to refresh.
Maybe you can try it another way, using converters. I have an app that does something like that. I usually need a test app to get this stuff just right, but try this:
<DataGridTextColumn Header="Requested" Binding="{Binding Requested}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="Foreground">
<Setter.Value>
<MultiBinding Converter="{StaticResource OverRequestedForegroundMultiConverter}">
<Binding Path="Requested" />
<Binding Path="Volume" />
</MultiBinding>
</Setter.Value>
</Setter>
<Setter Property="ToolTip">
<Setter.Value>
<MultiBinding Converter="{StaticResource OverRequestedTooltipMultiConverter}">
<Binding Path="Requested" />
<Binding Path="Volume" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</DataGridTextColumn.CellStyle>
The converters would look something like this:
public class OverRequestedForegroundMultiConverter : IMultiValueConverter
{
#region IValueConverter Members
public object Convert(object[] value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value.Length == 2)
{
if (value[0] is int && value[1] is int)
{
int requested = (int)value[0];
int volume = (int)value[1];
if (requested > volume)
return Colors.Red;
}
}
return Colors.Gray; // Or whatever color you want
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
public class OverRequestedTooltipMultiConverter : IMultiValueConverter
{
#region IValueConverter Members
public object Convert(object[] value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && value.Length == 2)
{
if (value[0] is int && value[1] is int)
{
int requested = (int)value[0];
int volume = (int)value[1];
if (requested > volume)
return "The requested volume is greater than the available volume";
}
}
return null;
}
public object[] ConvertBack(object value, Type[] targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
Don't forget to add the converters to your app.xaml:
<app:OverRequestedForegroundMultiConverter x:Key="OverRequestedForegroundMultiConverter" />
<app:OverRequestedTooltipMultiConverter x:Key="OverRequestedTooltipMultiConverter" />
How can I limit WPF DataGridTextColumn Text to max length of 10 characters.
I don't want to use DatagridTemplateColumn, because it has memory leak problems.
Also the field is bound to a data entity model.
If you don't want to use DatagridTemplateColumn then you can change DataGridTextColumn.EditingElementStyle and set TextBox.MaxLength there:
<DataGridTextColumn Binding="{Binding Path=SellingPrice, UpdateSourceTrigger=PropertyChanged}">
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="{x:Type TextBox}">
<Setter Property="MaxLength" Value="10"/>
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
I know I'm gravedigging a bit, but I came up with another solution to this that I didn't find anywhere else. It involves the use of a value converter. A bit hacky, yes, but it has the advantage that it doesn't pollute the xaml with many lines, which might be useful, if you want to apply this to many columns.
The following converter does the job. Just add the following reference to App.xaml under Application.Resources: <con:StringLengthLimiter x:Key="StringLengthLimiter"/>, where con is the path to the converter in App.xaml.
public class StringLengthLimiter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(value!=null)
{
return value.ToString();
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int strLimit = 3;
try
{
string strVal = value.ToString();
if(strVal.Length > strLimit)
{
return strVal.Substring(0, strLimit);
}
else
{
return strVal;
}
}
catch
{
return "";
}
}
}
Then just reference the converter in xaml binding like this:
<DataGridTextColumn Binding="{Binding Path=SellingPrice,
UpdateSourceTrigger=PropertyChanged,
Converter={StaticResource StringLengthLimiter}}">