How do I implement a CollectionLengthToVisibility converter? - c#

I want to implement a converter so that certain XAML elements only appear/disappear if there are items in an ObservableCollection.
I have referenced How to access generic property without knowing the closed generic type but cannot get it to work with my implementation. It build and deploys OK (to Windows Phone 7 emulator and device) but does not work. Moreover Blend throws an exception and will no longer render the page,
NullReferenceException: Object reference not set to an instance of an
object.
Here is what I have so far,
// Sets the vsibility depending on whether the collection is empty or not depending if parameter is "VisibleOnEmpty" or "CollapsedOnEmpty"
public class CollectionLengthToVisibility : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo cultureInfo)
{
// From https://stackoverflow.com/questions/4592644/how-to-access-generic-property-without-knowing-the-closed-generic-type
var p = value.GetType().GetProperty("Length");
int? length = p.GetValue(value, new object[] { }) as int?;
string s = (string)parameter;
if ( ((length == 0) && (s == "VisibleOnEmpty"))
|| ((length != 0) && (s == "CollapsedOnEmpty")) )
{
return Visibility.Visible;
}
else
{
return Visibility.Collapsed;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo cultureInfo)
{
return null;
}
}
Here is how I referenced the converter on Blend/XAML
<TextBlock Visibility="{Binding QuickProfiles, ConverterParameter=CollapsedOnEmpty, Converter={StaticResource CollectionLengthToVisibility}}">Some Text</TextBlock>

I would use the Enumerable.Any() extension method. It will work on any IEnumerable<T> and avoids you having to know what sort of collection you're dealing with. Since you don't know T you can just use .Cast<object>()
public class CollectionLengthToVisibility : System.Windows.Data.IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo cultureInfo)
{
var collection = value as System.Collections.IEnumerable;
if (collection == null)
throw new ArgumentException("value");
if (collection.Cast<object>().Any())
return Visibility.Visible;
else
return Visibility.Collapsed;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo cultureInfo)
{
throw new NotImplementedException();
}
}

Related

Dependency injection in IValueConverter MVVM Prism

I Want to pass Dependency injection in IValueConverter in MVVM (Prism).
I used ServiceLocator, but it not working with .Net Core 3.1.
please let me if any way to do Dependency injection in IValueConverter.
public class RowToColorConverter : MarkupExtension, IValueConverter
{
private ICommonInventoryReference _commonInventoryReference;
/// <summary>
/// back Event Args Converter
/// </summary>
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (_commonInventoryReference == null)
{
_commonInventoryReference = null;
} // CommonServiceLocator.ServiceLocator.Current.GetInstance<CommonInventoryReference>(); }
if (_commonInventoryReference == null || !(value is Inventory inventory)) { return Brushes.White; }
var brand = _commonInventoryReference.LoadBrandsByIdOrName(new BrandModel() { Name = inventory.Brand });
if (brand == null || string.Compare(brand.IsBrand, "YES", StringComparison.OrdinalIgnoreCase) == 0)
{
return Brushes.White;
}
var color = (Color)ColorConverter.ConvertFromString("#edecbb");
return new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, color.B));
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Use ContainerLocator instead, as seen in the relase notes.

IValueConverter Using Lookup Collection

I am using a converter like this:
public class BreedConverter : IValueConverter
{
static ObservableCollection<Breed_> Breeds = Breed_.GetBreeds();
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value != null && Breeds.Count > 0)
{
short breedID = (short)value;
Breed_ breed = Breeds.Single(s => s.BreedID == breedID);
return (string)breed.Breed;
}
else
return "";
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The Breeds collection is retrieved from a SQL Server database. I want to retrieve it once and then use it to do the conversion. I don't want to go to the database each and every time I need to convert.
Is there a better way to do this, e.g. ResourceDictionary (which I don't know how to use in this scenario, since I'm still a noob)?
This is what I have been able to get to work. As #Darthchai mentioned, I have created the collection in the class/window. However, I cannot figure out how to use INotifyPropertyChanged to let the specific TextBlock know to do the conversion. I am a noob, after all. :) What I was able to do - and I don't know if this is the right approach - was to use the Loaded event of the TextBlock to do the conversion. here is the code:
private void BreedTextBlock_Loaded(object sender, RoutedEventArgs e)
{
TextBlock textBlock = (TextBlock)sender;
if (textBlock != null && Breeds.Count > 0)
{
try
{
short breedID = short.Parse(textBlock.Text);
Breed_ breed = Breeds.Single(s => s.BreedID == breedID);
textBlock.Text = (string)breed.Breed;
}
catch
{
}
}
return;
}

Using FindResource in an IValueConverter

I have this value converter which converts a number to a brush color. What I need to do is to change the line return Brushes.Red; into return (Brush)FindResource("PrimaryHueMidBrush");, so I can return the color of the main theme. The problem is that I don't know how to declare (Brush)FindResource("PrimaryHueMidBrush");. Any help is welcome. Thank you in advance.
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double.TryParse(value.ToString(), out double val);
if (val == 1)
{
return Brushes.Red;
}
else if(val == 0.5)
{
return Brushes.MediumVioletRed;
}
else if(val==0)
{
return Brushes.Transparent;
}
else
{
return Brushes.Transparent;
}
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
Instead of calling FindResource in the converter, you'd better add one or more properties for the dynamic Brushes:
public class YourConverter : IValueConverter
{
public Brush FirstBrush { get; set; }
public Brush SecondBrush { get; set; }
public object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
double val = (double)value;
if (val >= 1)
{
return FirstBrush;
}
if (val >= 0.5)
{
return SecondBrush;
}
return Brushes.Transparent;
}
public object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
You would declare it in the Resources of your Application or Window like this:
<local:YourConverter x:Key="YourConverter"
FirstBrush="{StaticResource PrimaryHueMidBrush}"
SecondBrush="MediumVioletRed"/>
To access FindResource you need a FrameworkElement so the best way to do this would probably be to use a MultiValueConverter instead and pass the element that uses the converter as a second value.
Converter:
public class WhateverThisIsCalledConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Insert type- and sanity-checks here
double val = (double)values[0];
FrameworkElement callingElement = (FrameworkElement)values[1];
if (val >= 1)
{
return callingElement.FindResource("PrimaryHueMidBrush");
}
if (val >= 0.5)
{
return Brushes.MediumVioletRed;
}
return Brushes.Transparent;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
return Enumerable.Repeat(DependencyProperty.UnsetValue, targetTypes.Length).ToArray();
}
}
Usage in XAML:
<Window.Resources>
<local:WhateverThisIsCalledConverter x:Key="Converter"/>
<SolidColorBrush Color="Red" x:Key="PrimaryHueMidBrush"/>
</Window.Resources>
<Grid>
<Grid.Background>
<MultiBinding Converter="{StaticResource Converter}">
<Binding Path="Value"/>
<Binding RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
</Grid.Background>
</Grid>
Couple of notes on your current implementation:
Try avoiding == on doubles, they are not infinitely precice.
You don't need all those elses when you return in the if before.
The ConvertBack method should be implemented (Free choice of other Exceptions, Binding.DoNothing and DependencyProperty.UnsetValue).
If you know your value is a double, simply cast it instead.

The Type local:TextInputToVisibilityConverter Could Not Be Found

I was looking around for how to do a placeholder in WPF, and I found the answer here. I used the XAML Code in my file, and it gave me the following error: The Type local:TextInputToVisibilityConverter Could Not Be Found. The line looks like this:
<local:TextInputToVisibilityConverter x:Key="TextInputToVisibilityConverter" />
I'm confused why it's giving me this error because I have the TextInputToVisibilityConverter in my c# code:
public class TextInputToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Always test MultiValueConverter inputs for non-null
// (to avoid crash bugs for views in the designer)
if (values[0] is bool && values[1] is bool)
{
bool hasText = !(bool)values[0];
bool hasFocus = (bool)values[1];
if (hasFocus || hasText)
return Visibility.Collapsed;
}
return Visibility.Visible;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
Help would be greatly appreciated.
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Always test MultiValueConverter inputs for non-null
// (to avoid crash bugs for views in the designer)
if (values[0] is bool && values[1] is bool)
{
bool hasText = !(bool)values[0];
bool hasFocus = (bool)values[1];
if (hasFocus || hasText)
return Visibility.Collapsed;
}
return Visibility.Visible;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
string[] values = null;
if (value != null)
{
values = value.ToString().Split(' ');
return values;
}
else
{
return null;
}
}
You should have changed the same name converter x:key:name and should have filled the Convert back.

Demultiplexing using IMultiValueConverter

I have one DependencyProperty X (String) and n DependencyProperties Yi (String), all of type String, where X is the concatenation of Yi for all i. Using a MultiValueConverter this can be achieved easily.
On the other hand, if x changes, I want to split x into chunks and assign each chunk (by some ruleset that depends on the chunk's data value) to its corresponding y_i.
For this, I need to know which index j of the Object[] that ConvertBack returns is connected to y_i.
So I am wondering: How can I get a reference to the source object to whose's property the j-th value in the returned Object[] in ConvertBack is assigned
My Convert-Method:
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
StringBuilder b = new StringBuilder();
String cur;
for (Int32 i = 0; i < values.Length; i++)
{
if(String.IsNullOrEmpty(cur = values[i] as String)) continue;
if (b.Length != 0) b.Append(',');
b.Append(cur);
}
return b.ToString();
}
and the corresponding ConvertBack
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
Object[] ret = new Object[targetTypes.Length];
// How do I know which Source.Property the element
// ret[i]
// targets
}
I found a general (and somewhat practical) solution that worked for me
I implemented ConvertBack in IMultiValueConverter as follows
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
Object[] ret = new Object[targetTypes.Length];
if(null != (value as String))
for(Int32 i = 0; i < targetType.Length; i++)
{
// Propagate a copy of value to each Binding
ret[i] = value.ToString();
}
return ret;
}
This distributes the provided value to each attached Yi. Since a MultiBinding is a 'collection' of Binding-s, one can attach an IValueConverter to each Binding. Each IValueConverter can be parameterized with some information regarding its attached source, so it can filter out any chunks that do not belong to the attached source.
Attached an example of an IValueConverter that uses data provided by parameter to filter out values
[ValueConversion(typeof(String), typeof(String))]
public sealed class ParameterFilter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if(null == (parameter as String)) return DependencyProperty.UnsetValue;
if(null == (value as String)) return DependencyProperty.UnsetValue;
String[] split = value.ToString().Split(',');
StringBuilder b = new StringBuilder();
String cur;
for (Int32 i = 0; i < split.Length; i++)
{
if(String.IsNullOrEmpty(cur = split[i])) continue;
if(!cur.Contains(parameter.ToString()) continue;
if (b.Length != 0) b.Append(',');
b.Append(cur);
}
return b.ToString();
}
}

Categories

Resources