Scaling woes
-
Hi, I'm trying to get my mind around a problem I'm having with WPF. It can easily be compared to something like a stacked-bar chart (BTW, that's not what I'm creating, so please don't point me to a library providing these). Visually the results should look something like this:
NameA |-----||-----------||----| NameB |---------||-----------------| NameC |---||----------||-|
At the moment I have an Observable collection of data that provides me with two types, the string for the name and another collection with the values. I have used aListView
to display my table and the values are represented by anItemsControl
putting the data values inside a horizontally organizedStackPanel
.<ListView ItemsSource="{Binding Source={StaticResource ResourceKey=data}}" Grid.Row="0" x:Name="myGrid">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}"/>
<GridViewColumn Header="Values">
<GridViewColumn.CellTemplate>
<DataTemplate>
<!-- Bind each item to the valueTemplate -->
<ItemsControl ItemsSource="{Binding Path=Values}"
ItemTemplate="{StaticResource valueTemplate}">
<ItemsControl.ItemsPanel> -
Hi, I'm trying to get my mind around a problem I'm having with WPF. It can easily be compared to something like a stacked-bar chart (BTW, that's not what I'm creating, so please don't point me to a library providing these). Visually the results should look something like this:
NameA |-----||-----------||----| NameB |---------||-----------------| NameC |---||----------||-|
At the moment I have an Observable collection of data that provides me with two types, the string for the name and another collection with the values. I have used aListView
to display my table and the values are represented by anItemsControl
putting the data values inside a horizontally organizedStackPanel
.<ListView ItemsSource="{Binding Source={StaticResource ResourceKey=data}}" Grid.Row="0" x:Name="myGrid">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}"/>
<GridViewColumn Header="Values">
<GridViewColumn.CellTemplate>
<DataTemplate>
<!-- Bind each item to the valueTemplate -->
<ItemsControl ItemsSource="{Binding Path=Values}"
ItemTemplate="{StaticResource valueTemplate}">
<ItemsControl.ItemsPanel>Hi Ray, Looks like your first problem is you need to have some sort of "shared" scale factor. You won't be able to access the element named 'myScale'. Since it's defined in a DataTemplate, there would be any number of elements with this name, so it would only be valid in the DataTemplate definition. For the second problem, you say that you are using a TypeConverter, is that correct? If it is, then you will not be able to get the "values" refreshed automatically. Meaning you would have to refresh the bindings when the scale slider is moved. What you can do is this: 1. Create a separate class (e.g. ScaleFactor) with a single property (e.g. Value) 2. Implement INotifyPropertyChanged in the ScaleFactor class and fire it when the Value changes. 3. Create an IMultiValueConverter that takes two values: 1st will be the "value" from your underlying data (you don't have to use a TypeConverter, but you probably can), 2nd will be the scale factor. The IMultiValueConverter will then multiply the two values and return that. 4. Define an instance of ScaleFactor in the Window's resource section. 5. Define an instance of your converter from #3 in the Window's resource section. 6. Update the Border's width to be a MultiBinding using your converter with the "value" and scale factor (from #4) as your input values.
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
Hi Ray, Looks like your first problem is you need to have some sort of "shared" scale factor. You won't be able to access the element named 'myScale'. Since it's defined in a DataTemplate, there would be any number of elements with this name, so it would only be valid in the DataTemplate definition. For the second problem, you say that you are using a TypeConverter, is that correct? If it is, then you will not be able to get the "values" refreshed automatically. Meaning you would have to refresh the bindings when the scale slider is moved. What you can do is this: 1. Create a separate class (e.g. ScaleFactor) with a single property (e.g. Value) 2. Implement INotifyPropertyChanged in the ScaleFactor class and fire it when the Value changes. 3. Create an IMultiValueConverter that takes two values: 1st will be the "value" from your underlying data (you don't have to use a TypeConverter, but you probably can), 2nd will be the scale factor. The IMultiValueConverter will then multiply the two values and return that. 4. Define an instance of ScaleFactor in the Window's resource section. 5. Define an instance of your converter from #3 in the Window's resource section. 6. Update the Border's width to be a MultiBinding using your converter with the "value" and scale factor (from #4) as your input values.
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
Tom, thanks for your reply. Your suggestions make a lot of sense and have, I feel, pointed me in the right direction. Although I think I'm messing up on step 6! This is what I've done so far:
TJoe wrote:
1. Create a separate class (e.g. ScaleFactor) with a single property (e.g. Value) 2. Implement INotifyPropertyChanged in the ScaleFactor class and fire it when the Value changes.
public class VisualScale : INotifyPropertyChanged
{
private double scale = 1.0;
public double ScalingFactor
{
get { return scale; }
set { scale = value; NotifyPropertyChanged("Scale changed"); }
}#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}TJoe wrote:
3. Create an IMultiValueConverter that takes two values: 1st will be the "value" from your underlying data (you don't have to use a TypeConverter, but you probably can), 2nd will be the scale factor. The IMultiValueConverter will then multiply the two values and return that.
public class WidthScaler : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ( targetType != typeof(double) )
{
throw new NotSupportedException("Target type of WidthScaler must be double");
}if ( values.Length != 2 ||
&nb -
Tom, thanks for your reply. Your suggestions make a lot of sense and have, I feel, pointed me in the right direction. Although I think I'm messing up on step 6! This is what I've done so far:
TJoe wrote:
1. Create a separate class (e.g. ScaleFactor) with a single property (e.g. Value) 2. Implement INotifyPropertyChanged in the ScaleFactor class and fire it when the Value changes.
public class VisualScale : INotifyPropertyChanged
{
private double scale = 1.0;
public double ScalingFactor
{
get { return scale; }
set { scale = value; NotifyPropertyChanged("Scale changed"); }
}#region INotifyPropertyChanged Members
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}TJoe wrote:
3. Create an IMultiValueConverter that takes two values: 1st will be the "value" from your underlying data (you don't have to use a TypeConverter, but you probably can), 2nd will be the scale factor. The IMultiValueConverter will then multiply the two values and return that.
public class WidthScaler : IMultiValueConverter
{
#region IMultiValueConverter Members
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if ( targetType != typeof(double) )
{
throw new NotSupportedException("Target type of WidthScaler must be double");
}if ( values.Length != 2 ||
&nbI'm going to reply to myself as looking at the code I wrote I'd made an obvious mistake. Note, I still get the same exception but the following code is now used:
<Slider x:Name="scaleSlider" Width="100" Minimum="0.5" Maximum="10.0"
Value="{Binding ElementName=scale, Path=ScalingFactor}"/>and
<Border.Width>
<MultiBinding Converter="{StaticResource widthScaler}"
ConverterParameter="">
<Binding Path="{Binding Duration}" />
<Binding ElementName="scale" Path="ScalingFactor"/>
</MultiBinding>
</Border.Width>Hopefully this is now a little closer to the correct solution! Is it? :-\
Regards, Ray
-
I'm going to reply to myself as looking at the code I wrote I'd made an obvious mistake. Note, I still get the same exception but the following code is now used:
<Slider x:Name="scaleSlider" Width="100" Minimum="0.5" Maximum="10.0"
Value="{Binding ElementName=scale, Path=ScalingFactor}"/>and
<Border.Width>
<MultiBinding Converter="{StaticResource widthScaler}"
ConverterParameter="">
<Binding Path="{Binding Duration}" />
<Binding ElementName="scale" Path="ScalingFactor"/>
</MultiBinding>
</Border.Width>Hopefully this is now a little closer to the correct solution! Is it? :-\
Regards, Ray
Hi Ray, Following the exception, this line is bad:
<Binding Path="{Binding Duration}" />
So here you are binding the Path property of a Binding object to the Value stored in the Duration property. You are basically double defining your binding statement. This needs to be:
<Binding Path="Duration" />
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
Hi Ray, Following the exception, this line is bad:
<Binding Path="{Binding Duration}" />
So here you are binding the Path property of a Binding object to the Value stored in the Duration property. You are basically double defining your binding statement. This needs to be:
<Binding Path="Duration" />
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
Ok, that makes sense too. When I run (or indeed the designer does the same). I've now got an exception being fired by the WidthScaler
if ( values.Length != 2 ||
values[0].GetType() != typeof(double) ||
values[1].GetType() != typeof(double) )
{
throw new NotSupportedException("Source values should be a pair of doubles");
}The debugger shows that the first value, Duration, is correct. But the second, is
DependancyProperty.UnsetValue
. If I remove the throwing of an exception and return null, the WidthScaler (the IMultiValueConverter) doesn't seem to be called again!Regards, Ray
-
Ok, that makes sense too. When I run (or indeed the designer does the same). I've now got an exception being fired by the WidthScaler
if ( values.Length != 2 ||
values[0].GetType() != typeof(double) ||
values[1].GetType() != typeof(double) )
{
throw new NotSupportedException("Source values should be a pair of doubles");
}The debugger shows that the first value, Duration, is correct. But the second, is
DependancyProperty.UnsetValue
. If I remove the throwing of an exception and return null, the WidthScaler (the IMultiValueConverter) doesn't seem to be called again!Regards, Ray
Sorry, your second binding is incorrect also. You have:
<Binding ElementName="scale" Path="ScalingFactor"/>
But "scale" is not a named element, it is a resource. To access it, you need to set the Binding.Source property like so:
<Binding Source="{StaticResource scale}" Path="ScalingFactor"/>
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
Sorry, your second binding is incorrect also. You have:
<Binding ElementName="scale" Path="ScalingFactor"/>
But "scale" is not a named element, it is a resource. To access it, you need to set the Binding.Source property like so:
<Binding Source="{StaticResource scale}" Path="ScalingFactor"/>
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
Great! The application now runs, but moving the slider doesn't seem to result in the MultiBinding being called again (break-pointing doesn't stop the code).
Regards, Ray
Did you update the binding there as well? It has the same problem as before. Make sure to look at the Output window of Visual Studio, that will have any binding errors listed. Also, make sure the Binding for the slider has Mode=TwoWay.
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
Did you update the binding there as well? It has the same problem as before. Make sure to look at the Output window of Visual Studio, that will have any binding errors listed. Also, make sure the Binding for the slider has Mode=TwoWay.
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
I've now got this, but it still doesn't look like it works: Xaml is now:
<Slider x:Name="scaleSlider" Width="100" Minimum="0.5" Maximum="10.0" Value="{Binding Source=scale, Path=ScalingFactor, Mode=TwoWay}"/>
I'd changed the binding in the MultiBinding to:
<Binding Source="{StaticResource scale}" Path="ScalingFactor"/>
Error in Output window is: System.Windows.Data Error: 35 : BindingExpression path error: 'ScalingFactor' property not found on 'object' ''String' (HashCode=-528916476)'. BindingExpression:Path=ScalingFactor; DataItem='String' (HashCode=-528916476); target element is 'Slider' (Name='scaleSlider'); target property is 'Value' (type 'Double') I'm sure the error will help, once I understand it! ;-)
Regards, Ray
-
I've now got this, but it still doesn't look like it works: Xaml is now:
<Slider x:Name="scaleSlider" Width="100" Minimum="0.5" Maximum="10.0" Value="{Binding Source=scale, Path=ScalingFactor, Mode=TwoWay}"/>
I'd changed the binding in the MultiBinding to:
<Binding Source="{StaticResource scale}" Path="ScalingFactor"/>
Error in Output window is: System.Windows.Data Error: 35 : BindingExpression path error: 'ScalingFactor' property not found on 'object' ''String' (HashCode=-528916476)'. BindingExpression:Path=ScalingFactor; DataItem='String' (HashCode=-528916476); target element is 'Slider' (Name='scaleSlider'); target property is 'Value' (type 'Double') I'm sure the error will help, once I understand it! ;-)
Regards, Ray
The two bindings you just posted are not the same. Compare them carefully (ignoring the fact that Mode is set on one of them though).
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
The two bindings you just posted are not the same. Compare them carefully (ignoring the fact that Mode is set on one of them though).
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
Ok, given that they're both working on a source of "scale" and a Path of ScalingFactor (ignoring Mode), that leaves the fact that the second isn't a StaticResource. I'm not certain how to convert between the two markup conventions (although I guess I could expand out the Slider's "Value" element). I tried this and it doesn't change anything :-(
<Slider x:Name="scaleSlider" Width="100" Minimum="0.5" Maximum="10.0" Value="{Binding Source={StaticResource scale}, Path=ScalingFactor, Mode=TwoWay}"/>
Regards, Ray
-
Ok, given that they're both working on a source of "scale" and a Path of ScalingFactor (ignoring Mode), that leaves the fact that the second isn't a StaticResource. I'm not certain how to convert between the two markup conventions (although I guess I could expand out the Slider's "Value" element). I tried this and it doesn't change anything :-(
<Slider x:Name="scaleSlider" Width="100" Minimum="0.5" Maximum="10.0" Value="{Binding Source={StaticResource scale}, Path=ScalingFactor, Mode=TwoWay}"/>
Regards, Ray
You still get the same binding error with that change?
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
Ok, given that they're both working on a source of "scale" and a Path of ScalingFactor (ignoring Mode), that leaves the fact that the second isn't a StaticResource. I'm not certain how to convert between the two markup conventions (although I guess I could expand out the Slider's "Value" element). I tried this and it doesn't change anything :-(
<Slider x:Name="scaleSlider" Width="100" Minimum="0.5" Maximum="10.0" Value="{Binding Source={StaticResource scale}, Path=ScalingFactor, Mode=TwoWay}"/>
Regards, Ray
Sorry, I noticed that you did not implement INotifyPropertyChanged correct. The parameter being passed needs to be the name of the property that changed, not some random text. Otherwise the system will never know where to look for the changed.
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
Sorry, I noticed that you did not implement INotifyPropertyChanged correct. The parameter being passed needs to be the name of the property that changed, not some random text. Otherwise the system will never know where to look for the changed.
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
-
Sorry, I noticed that you did not implement INotifyPropertyChanged correct. The parameter being passed needs to be the name of the property that changed, not some random text. Otherwise the system will never know where to look for the changed.
Take care, Tom ----------------------------------------------- Check out my blog at http://tjoe.wordpress.com
Hi, Kindly suggest me how to use a DP as ConverterParameter in Xaml. Thnx, Ritesh