im trying to align my Popup to the bottom center of my Window, how ever i'm getting this error:
Additional information: Specified cast is not valid.
This is being caused by my converter double windowWidth = (double)values[0];, how ever the ActualWidth should bee a double! Not too sure on what is going wrong here.
I'm currently showing the data in a MessageBox just to test it at the moment and make sure the values look correct.
Converter
namespace Test_Project.Converters
{
public class NotificationOffsets : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
double windowWidth = (double)values[0];
double notificationWidth = (double)values[1];
MessageBox.Show("Notification Width: " + notificationWidth.ToString() + " Window Width: " + windowWidth.ToString());
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
}
XAML - Converter Binding
<Style TargetType="Popup" x:Key="PopupNotification">
<Setter Property="IsOpen" Value="True" />
<Setter Property="HorizontalOffset">
<Setter.Value>
<MultiBinding Converter="{StaticResource NotificationOffsets}">
<Binding RelativeSource="{RelativeSource Self}" Path="PlacementTarget.ActualWidth" />
<Binding RelativeSource="{RelativeSource Self}" Path="ActualWidth" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
Edit:
Breakpoint Data:
Edit 2:
I have now set my PlacementTarget within my Style:
<Setter Property="PlacementTarget" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" />
Still getting the same error!
You have to return a double on your converter, you are returning a boolean, which cause the invalid cast.
EDIT: You are having Binding problems, you have to set the "PlacementTarget" of your popup in order the get the Width property.
EDIT 2: try this:
<Window x:Class="WpfApplication7.MainWindow"
Name="myWindow"
............
<Setter Property="PlacementTarget" Value="{Binding ElementName=myWindow}"/>
Related
I have a converter that maps a percentage into a range. It has two dependency properties for storing the start and end of the range. Below is an example of it's declaration:
<local:PercentToRangeConverter RangeStart="50" RangeEnd="200" />
I'm using the converter in a style for a CustomControl's Width where the MinWidth and the MaxWidth is bound to the RangeStart and the RangeEnd of the converter. Unfortunately, it doesn't work. I've already made sure that the converter itself is working by manually setting the values of the RangeStart and the RangeEnd with an arbitrary number.
I've done two ways for binding the properties of the CustomControl to the converters:
Declare the converter inside the Style.Resources, bind the properties inside there, then use the converter as a static resource.
<Style TargetType="{x:Type local:MyControl}">
<Style.Resources>
<local:PercentToRangeConverter x:Key="PercentToRangeConverter"
RangeStart="{Binding MinWidth, RelativeSource={RelativeSource Self}"
RangeEnd="{Binding MaxWidth, RelativeSource={RelativeSource Self}" />
</Style.Resources>
. . .
Declare the converter inside the converter property of a Binding tag inside the setter's value
. . .
<Setter Property="Width">
<Setter.Value>
<Binding Path="Percent" RelativeSource="{RelativeSource Self}">
<Binding.Converter>
<local:PercentToRangeConverter
RangeStart="{Binding MinWidth, RelativeSource={RelativeSource Self}"
RangeEnd="{Binding MaxWidth, RelativeSource={RelativeSource Self}" />
</Binding.Converter>
</Binding>
</Setter.Value>
</Setter>
. . .
</Style>
Again, these work when I manually set the value. So I think RelativeSource={RelativeSource Self} is incorrect for the binding but I can't think of anything else so that I can bind the CustomControl's Width. I also think that both methods don't have the same proper way for setting the source considering that the way the converter is declared is different for both methods. Either way, I just want to succesfully bind the MinWidth and MaxWidth to the converter.
P.S. Is "declare" even the proper term for that?
EDIT: Here's the code for the Converter as requested.
public class PercentToRangeConverter : DependencyObject, IValueConverter
{
public double RangeStart
{
get => (double)GetValue(RangeStartProperty);
set => SetValue(RangeStartProperty, value);
}
protected static readonly DependencyProperty RangeStartProperty = DependencyProperty.Register("RangeStart", typeof(double), typeof(PercentToRangeConverter), new PropertyMetadata(default(double)));
public double RangeEnd
{
get => (double)GetValue(RangeEndProperty);
set => SetValue(RangeEndProperty, value);
}
protected static readonly DependencyProperty RangeEndProperty = DependencyProperty.Register("RangeEnd", typeof(double), typeof(PercentToRangeConverter), new PropertyMetadata(default(double)));
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value * (RangeEnd - RangeStart) + RangeStart;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
EDIT (1): Forgot to add, I'm actually searching for answers other than MultiBinding. The reason is I'm animating the Percent value. I thought that MultiBinding might affect the performance.
I have multibinding on Image control. I bind two properties one is type of bool(IsLogged) and one is typeof Uri (ProfilePhoto).
XAML:
<Image.Source >
<MultiBinding Converter="{StaticResource avatarConverter}">
<Binding Path="ProfilePhoto"></Binding>
<Binding Path="StatusInfo.IsLogged"></Binding>
</MultiBinding>
</Image.Source>
</Image>
I create converter, which convert BitmapImage to gray scale if property IsLogged is false.
It look like this:
public class AvatarConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var image = values[0] as BitmapImage;
string s = values[1].ToString();
bool isLogged = System.Convert.ToBoolean(s);
if (!isLogged)
{
try
{
if (image != null)
{
var grayBitmapSource = new FormatConvertedBitmap();
grayBitmapSource.BeginInit();
grayBitmapSource.Source = image;
grayBitmapSource.DestinationFormat = PixelFormats.Gray32Float;
grayBitmapSource.EndInit();
return grayBitmapSource;
}
return null;
}
catch (Exception ex)
{
throw ex;
}
}
return image;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
It works good if only I bind on image source property type fo BitmapImage, but I need bind property type of Uri.
I have a fear of the creation variable BitmapImage in converter and as source use Uri.
An return this variable as Source of image. I think this is not ideal way. Maybe I am wrong.
What is your opinion
Some elegant solution?
Although you can do it with a converter, there is a much better option: using a shader effect. You'll find an implementation of a GreyscaleEffect on this page.
<Style x:Key="grayedIfNotLogged" TargetType="Image">
<Style.Triggers>
<DataTrigger Binding="{Binding StatusInfo.IsLogged}" Value="False">
<Setter Property="Effect">
<Setter.Value>
<fx:GrayscaleEffect />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
...
<Image Source="..." Style="{StaticResource grayedIfNotLogged}" />
At the moment I have a date time axis where the date is in-line with the points, is there anyway to get this date to appear in the center such as on a bar chart.
<Style x:Key="DateTimeAxisLabelStyle2" TargetType="chartingToolkit:DateTimeAxisLabel">
<Setter Property="DaysIntervalStringFormat" Value="{}{0:dd-MMM}" />
<Setter Property="HoursIntervalStringFormat" Value="{}{0:hh:mm tt}" />
<!--<Setter Property="RenderTransformOrigin" Value="1,0.5" />
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="-45" />
</Setter.Value>
</Setter>-->
<!--<Setter Property="Margin" Value="30,0,-10,0" />-->
</Style>
<chartingToolkit:DateTimeAxis IntervalType="Days"
Interval="1"
Minimum="{Binding StartDate}"
Maximum="{Binding EndDate}"
Orientation="X"
VerticalContentAlignment="Center"
Title="Day"
AxisLabelStyle="{StaticResource DateTimeAxisLabelStyle2}" />
Any help would be greatly appreciated.
Here's what i got:
XAML:
<Window.Resources>
<Style x:Key="DateTimeAxisLabelStyle1" TargetType="{x:Type chartingToolkit:DateTimeAxisLabel}">
<Setter Property="DaysIntervalStringFormat" Value="{}{0:dd-MMM}"></Setter>
<Setter Property="RenderTransformOrigin" Value="0.80,0.20"></Setter>
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="-90"></RotateTransform>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<chartingToolkit:Chart Margin="0" Title="Chart Title">
<chartingToolkit:Chart.DataContext>
<local:MyDataCollection/>
</chartingToolkit:Chart.DataContext>
<chartingToolkit:Chart.Axes>
<chartingToolkit:DateTimeAxis Minimum="{Binding StartDate}" Maximum="{Binding EndDate}" Orientation="X" ShowGridLines="True" AxisLabelStyle="{DynamicResource DateTimeAxisLabelStyle1}"/>
</chartingToolkit:Chart.Axes>
<chartingToolkit:LineSeries DependentValuePath="Y" IndependentValuePath="X" ItemsSource="{Binding}"/>
</chartingToolkit:Chart>
</Grid>
Chart:
Here's what I did using the WPF Toolkit Source for reference.
I created a custom class deriving from DateTimeAxis, then overrode the "GetPlotAreaCoordinate" method. The DateTimeAxis.Render() calls that method three times with the same list of "DateTime" values, once for the MajorTickmarks, once for MinorTickmarks, and once for the date label. There were no minor tickmarks in the list, so the method was actually only getting called twice. I just keep a list of the values that have been evaluated and assume that if it's in the list it's already done the tickmarks and is now doing the Labels.
class CustomDateTimeAxis : DateTimeAxis
{
List<object> _valueList = new List<object>();
UnitValue prevBaseValue;
protected override UnitValue GetPlotAreaCoordinate(object value, Range<IComparable> currentRange, double length)
{
_valueList.Add(value);
UnitValue baseValue = base.GetPlotAreaCoordinate(value, currentRange, length);
int valueCount = _valueList.Count((x) => x.Equals(value));
if (valueCount == 2)
return new UnitValue(baseValue.Value + 27, baseValue.Unit);
prevBaseValue = baseValue;
return baseValue;
}
protected override void Render(Size availableSize)
{
base.Render(availableSize);
_valueList.Clear();
}
}
"27" is just a number I was trying out. You might want to play with that to see what works best for you.
return new UnitValue(baseValue.Value + 27, baseValue.Unit);
I created a Margin-Converter:
public class MarginConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var dateTimeAxis = values[0] as DateTimeAxis; ;
var actualAxisLength = values[1] as double?;
var actualMaximum = values[2] as DateTime?;
var actualMinimum = values[3] as DateTime?;
if (dateTimeAxis == null ||
!dateTimeAxis.Interval.HasValue ||
!actualAxisLength.HasValue ||
!actualMaximum.HasValue ||
!actualMinimum.HasValue)
return null;
double xMargin = 0;
var interval = dateTimeAxis.Interval.Value;
var timeSpan = actualMaximum.Value - actualMinimum.Value;
var timeSpanInDays = timeSpan.TotalDays;
if (dateTimeAxis.IntervalType == DateTimeIntervalType.Months)
{
xMargin = 30 * interval * actualAxisLength.Value / timeSpanInDays;
}
else if (dateTimeAxis.IntervalType == DateTimeIntervalType.Days)
{
xMargin = interval * actualAxisLength.Value / timeSpanInDays;
}
return new Thickness(xMargin, 10, 0, -30);
}
public object[] ConvertBack(object value, System.Type[] targetType, object parameter, CultureInfo culture)
{
return null;
}
}
called the X-Axis 'SharedXAxis' and used the converter like this:
<Setter Property="Margin">
<Setter.Value>
<MultiBinding Converter="{StaticResource MarginConv}">
<Binding ElementName="SharedXAxis"/>
<Binding ElementName="SharedXAxis" Path="ActualWidth"/>
<Binding ElementName="SharedXAxis" Path="ActualMaximum"/>
<Binding ElementName="SharedXAxis" Path="ActualMinimum"/>
</MultiBinding>
</Setter.Value>
Imo this should be full dynamic.
The Top- and Bottom-Values of the Thickness of the Margin-Converter ('10' and '-30' in my case) as well as the Bottom-Value of the Padding of the Chart itself have to be adjusted, I don't know why.
So I'm trying to Bind a TextBlock to multiple values on my Viewmodel (Mix of Enums and Strings). I have a DataTrigger that is supposed to fire when the text is null when returned by the Converter. But it doesn't! At first, I thought my Style didn't take hold (hence changed the Background on the Style to show it did). Anyway here is the code
XAML
<TextBlock x:Name="MyTextBlock" Grid.Column="2" Grid.ColumnSpan="3" VerticalAlignment="Center" DataContext="{StaticResource ViewModelLocator}"
Margin="{Binding RelativeSource={RelativeSource Self}, Path=(params:General.BoldPadding), Mode=OneWay}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource GeneralMultiStringDisplayConverter}">
<Binding Path="RatesViewModel.Instrument.Currency" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="RatesViewModel.Instrument.Underlying" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="RatesViewModel.Instrument.ProductType" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
</TextBlock.Text>
<TextBlock.Resources>
<Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource HeaderTextStyle}">
<Setter Property="Background" Value="Blue"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyTextBlock, Path=Text}" Value="{x:Null}"> <!--THIS SHOULD FIRE-->
<Setter Property="Text" Value="ThisShouldFireOnStart"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Resources>
</TextBlock>
The Converter is as follows:
class GeneralMultiStringDisplayConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var AS = DependencyProperty.UnsetValue;
if (values[0] != AS )
{
int count = values.Count();
string result = string.Empty;
for (int i = 0; i < count - 1; ++i)
{
try
{
var A = Enum.GetName((values[i].GetType()), values[i]);
result = String.Format("{0}{1}.", result, A);
}
catch (Exception ex)
{
result = String.Format("{0}{1}.", result, values[i]);
}
}
result = String.Format("{0}{1}", result, values[count - 1]);
return result;
}
return null;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
//TODO:
}
}
Debugging Steps that I have taken
`<Setter Property="Text" Value="ABC"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=MyTextBlock, Path=Text}" Value="ABC">
<Setter Property="Text" Value="ThisShouldFireOnStart"/>
</DataTrigger>
</Style.Triggers>
`
Added a converter to the Styles DataTrigger Binding. It always gets "" as a parameter and not null for some reason. Setting the trigger Value to "" does not work.
Added a default Text property in the Style and tried changing the Value based off of that. (See example above)
I would appreciate some help in getting this to work
Thanks!
You can not override these local bindings with a DataTrigger in a Style.
See Why doesn’t my WPF Trigger work?
If you just need to show a string if your bindings are null use TargetNullValue="MyNullValueString"
Perhaps you need a if(value[0] != null) condition in your converter to identify unset properties. Since i do not have any detail of your RatesViewModel i can not say for sure.
<MultiBinding Converter="{StaticResource GeneralMultiStringDisplayConverter}" TargetNullValue="ThisIsNull">
<Binding Path="RatesViewModel.Instrument.Currency" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="RatesViewModel.Instrument.Underlying" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
<Binding Path="RatesViewModel.Instrument.ProductType" NotifyOnSourceUpdated="True" UpdateSourceTrigger="PropertyChanged"/>
</MultiBinding>
<UserControl.Resources>
<DataTemplate x:Key="EditCardNonBuffer">
...
...
<ComboBox.SelectedValue>
<MultiBinding Mode="TwoWay" Converter="{StaticResource ingredientRowToTypeIDConverter}">
<Binding Path="GridIngredient.IngredientIngredientTypeRow" />
<Binding Path="GridIngredient.IngredientStockRowByIngredientStockGridIngredient" />
</MultiBinding>
</ComboBox.SelectedValue>
...
...
</DataTemplate>
<DataTemplate x:Key="EditCardBuffer">
...
...
<ComboBox.SelectedValue>
<MultiBinding Mode="TwoWay" Converter="{StaticResource ingredientRowToTypeIDConverter}">
<Binding Path="GridIngredient.IngredientIngredientTypeRow" />
<Binding Path="GridIngredient.IngredientStockRowByIngredientStockGridIngredient" />
</MultiBinding>
</ComboBox.SelectedValue>
...
...
</DataTemplate>
</UserControl.Resources>
<Border BorderBrush="Black" BorderThickness="1" Background="White" Margin="2,-4,2,0" Visibility="{Binding GridIngredient.IsEditMode, Converter={StaticResource GlobalBooleanToVisibilityConverter}}">
<ContentControl Content="{Binding}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding GridIngredient.IngredientIngredientTypeRow.IngredientTypeRow.IsBuffer}" Value="False">
<Setter Property="ContentTemplate" Value="{StaticResource EditCardNonBuffer}" />
</DataTrigger>
<DataTrigger Binding="{Binding GridIngredient.IngredientIngredientTypeRow.IngredientTypeRow.IsBuffer}" Value="True">
<Setter Property="ContentTemplate" Value="{StaticResource EditCardBuffer}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</Border>
Converter:
public class IngredientRowToTypeIDConverter : IMultiValueConverter
{
private SystemDataSet _dataSet;
public object Convert( object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture )
{
if( values.Length > 0 && values[ 0 ] is SystemDataSet.IngredientIngredientTypeRow )
{
var row = (SystemDataSet.IngredientIngredientTypeRow) values[ 0 ];
_dataSet = _dataSet ?? (SystemDataSet) row.Table.DataSet;
return row.IngredientTypeID;
}
else
MessageBox.Show( "just for debugging" );
return values;
}
public object[] ConvertBack( object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture )
{
if( value is int && _dataSet != null )
{
var ingredientTypeID = (int) value;
var ingredient = _dataSet.IngredientIngredientType.Where( r => r.IngredientTypeID == ingredientTypeID ).OrderBy( r => r.IngredientRow.LongName ).First();
var ingredientStock = ingredient.IngredientTypeRow.IsBuffer ? ingredient.IngredientRow.IngredientStockRowByDefaultBuffer : ingredient.IngredientRow.IngredientStockRowByDefaultNonBuffer;
return new object[] { ingredient, ingredientStock };
}
else
MessageBox.Show( "just for debugging" );
return new object[] { value };
}
}
Following exception is caught by Application.DispatcherUnhandledException event when I change selected value of combo box.
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
at System.Collections.Generic.List`1.get_Item(Int32 index)
at System.Windows.Data.MultiBindingExpression.UpdateSource(Object convertedValue)
at System.Windows.Data.BindingExpressionBase.UpdateValue()
at System.Windows.Data.BindingExpressionBase.Dirty()
at System.Windows.Data.BindingExpressionBase.SetValue(DependencyObject d, DependencyProperty dp, Object value)
at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal)
at System.Windows.DependencyObject.SetCurrentValueInternal(DependencyProperty dp, Object value)
at System.Windows.Controls.Primitives.Selector.UpdatePublicSelectionProperties()
at System.Windows.Controls.Primitives.Selector.SelectionChanger.End()
at System.Windows.Controls.Primitives.Selector.SelectionChanger.SelectJustThisItem(ItemInfo info, Boolean assumeInItemsCollection)
at System.Windows.Controls.ComboBoxItem.OnMouseLeftButtonUp(MouseButtonEventArgs e)
But I don't get it when the global exception handler event is not registered.
EDIT:
Notice that one of the bound values of combo box is also bound to a data trigger of the content control to change its data template. The exception ONLY happens when the change in combo box is supposed to change the template as well. The ContentTemplate changes successfully. But why the exception?