Xamarin.Forms. XAML Label IsVisible condition is not getting evaluated as expected - c#

In my project I have to select several options on a page.
Every option has a type MyCustomType and has public string Name property declared.
Every option is displayed via label. When I click on the label I display list of options and select it.
As one option is selected, an empty label with placeholder text (like select an item) for another option should appear below the label with the just selected option.
I use separate labels for every option, not a ListView element (customer's requirement for the particular look & feel).
Number of options is limited, let say it equals to four.
In my viewmodel I have declared the list property (it has been initialized in viewmodel constructor):
public List<MyCustomType> AllOptions { get; }
In my XAML page labels are declared as:
<Label Text="{Binding AllOptions[0].Name}" >
<Label Text="{Binding AllOptions[1].Name}" IsVisible="{Binding AllOptions[0], Converter={StaticResource NullToFalseBoolConverter}}">
<Label Text="{Binding AllOptions[2].Name}" IsVisible="{Binding AllOptions[1], Converter={StaticResource NullToFalseBoolConverter}}">
<Label Text="{Binding AllOptions[3].Name}" IsVisible="{Binding AllOptions[2], Converter={StaticResource NullToFalseBoolConverter}}">
Converter NullToFalseBoolConverter looks like that:
public class NullToFalseBoolConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value != null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
The problem is that IsVisible condition, specified into label declarations does not work.
All labels are displayed.
And breakpoint, set to the first line of Convert method of NullToFalseBoolConverter is not getting reached.
I don't understand why does it happen.
Any ideas?

Instead of trying binding in that matter, consider using either a layout type that supports both an ItemSource and also a data template. So this would be something like a ListView/CollectionView/Stacklayout
So, for example, if you decide to use a StackLayout for example:
<StackLayout
...
BindableLayout.ItemsSource="{Binding AllOptions}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<Label Text="{Binding Name}" IsVisible="{Binding ., Converter={StaticResource NullToFalseBoolConverter}}">
</DataTemplate>
</BindableLayout.ItemTemplate>
</StackLayout>
The beauty of this approach is that since your labels all follow the same approach you can now not only A) write cleaner code, and B) leverage MVVM patterns. Now, of course this does mean that each control gets the converters applied to them as well; however, if still want to not include it for the first element then all we have to do is change your type to include an index property. If you're wondering what the syntax Binding . means, it means that we are just binding the whole object of that collection for that element.
public int Index {get; set;}
Set it where you build that array, and then in the converter all you have to do is:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var element = (MyCustomType) value;
if(element.Index != 0)
return value != null;
else
return true;
}
EDIT
So as an addition to what you want to do in terms of passing the selected item to your command you can do the following. I am assuming that your overall page is a , but the same concept applies to any type of page really.
Set an x:Name property on the , give it any name you want. ex:
<ContentPage
...
x:Name="root">
Define a <GestureRecognizer> on your Label
<Label Text="{Binding Name}" IsVisible="{Binding ., Converter={StaticResource NullToFalseBoolConverter}}"> <Label.GestureRecognizers> <TapGestureRecognizer Command="{Binding BindingContext.YourCommandName, Source={x:Reference root}}" CommandParameter="{Binding .}"/> </Label.GestureRecognizers>
In your view model that is bond to the page, create the following command as follows:
public ICommand YourCommandName => new Command(x =>
YourCustomMethodHere(x));
Finally create the method that handles the object you selected
public void YourCustomMethodHere(MyCustomType type) {}
So what the above XAML code does is that we are binding the command of the Label to the overall parent view model, when an item is inside of a DataTemplate that has been defined by its ItemSource, its view model is actually the model that is being used a data template; that is why we are setting its source VM to be the one for the overall parent. The CommandParameter="{Binding .}" is the same logic as before, we are binding that whole data template item, in this case the MyCustomType that has been rendered for that element. This way each time that label is tapped, we are passing that label and its data to the command that we now defined in the VM.

That's funny, but the following approach fixed my problem.
I've had AllOptions declared as List (did not work), and as ObservableCollection (did not work too).
I should declare my list as array:
public MyCustomType[] AllOptions { get; }
And my labels start displaying properly, one after another is set.
And upon processing its values, if I get null value, that means, that we're reached the end of populated options.

Related

Run-time culture change and IValueConverter in a binding

The application I'm working on has an option to switch the interface language. This successfully changes most of the UI itself which uses, for example,
<Run Text="{Binding Resources.CreditsTitle, Source={x:Static models:ResourceService.Current}, Mode=OneWay}" />
Aside from static UI elements there are some displaying a few models' properties; for most of them no localisation is necessary, save for just one (for now). The models come from a different project within the solution, and are read-only. The one property that needs to be displayed in its localised form is represented by its ID in the model, and I wrote a converter for it that returns a Resource string.
public class RankIDToRankNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is int)
{
var ID = (int)value;
switch (ID)
{
case 1:
return Resources.Rank_Expert;
case 2:
return Resources.Rank_Intermediate;
And so on.
In the XAML document it gets bound as
<Run Text="{Binding Model.RankID, Converter={StaticResource RankIDToRankNameConverterKey}, Mode=OneWay}"
Style="{DynamicResource PickupTextElementStyleKey}"
FontSize="14" />
…and it works, but just once, when the UI element gets drawn for the first time (obviously it also works when the RankID property changes but that happens extremely rarely). It doesn't change when culture is changed. I'm guessing it's because the property changed event does not fire, so WPF sees no reason to update it.
What's the “proper” way to get it to update when culture is changed?
I have solved this issue by adding a new method to the underlying model. Since I'm using Livet and models are NotificationObject descendants, I added a new public method Update() to the model in question with
this.RaisePropertyChanged("changed_property_name_here");
and adding
try
{
InstanceOfModelStorage.UnderlyingModel.Update();
}
catch (NullReferenceException e) { }
to the culture change handler.

Avoiding infinite loop in ItemsControl having radiobutton value set by Converters

I have this code in WPF
Every new form gets added by clicking on "Add New" to the Itemscontrol.
Event is a CSLA call.
<Menu Grid.Row="0">
<MenuItem Header="Add New"
csla:InvokeMethod.MethodName="AddNew"
csla:InvokeMethod.TriggerEvent="Click" />
</Menu></ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate >
<DataTemplate.Resources>
<FrameworkElement x:Key="ReqProxyElement" DataContext="{Binding}" />
</DataTemplate.Resources>
<Grid>
<ContentControl Visibility="Collapsed" Content="{StaticResource ReqProxyElement}" />
<Grid>
<Grid.DataContext>
<formviewmodels:ReqViewModel Model="{Binding Source={StaticResource ReqProxyElement}, Path=DataContext}" />
</Grid.DataContext>
<formviews:ReqView />
</Grid>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now inside the form ReqView, I have converter call for radio button.
<Label Grid.Row="10" Grid.Column="0" Content="required" />
<StackPanel Orientation="Horizontal" Grid.Row="10" Grid.Column="1" >
<!--<RadioButton Content="Yes" GroupName="Required" IsChecked="{Binding Model.Required, Converter={StaticResource NullableBooleanToFalseConverter}}"></RadioButton>
<RadioButton Content="No" GroupName="Required" IsChecked="{Binding Model.Required, Converter={StaticResource ReverseBoolean}}"></RadioButton>-->
<RadioButton Content="Yes" GroupName="GRequired" ></RadioButton>
<RadioButton Content="No" GroupName="GRequired" ></RadioButton>
</StackPanel>
In this scenario when I click on add New , the ItemsControl as is the nature of the beast tries to bind back to the Form and goes into an infinite loop in the converter call.
The converter code is given below.
public class ReverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
return (!((bool)value));
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool)
{
return (!((bool)value));
}
return value;
}
}
public class NullableBooleanToFalseConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null)
{
return false;
}
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return value;
}
}
Can any one come up with a solution where the convereter don't kick the code into infinite loop.
What happens is when a Add New is clicked, if there is already a form in the Itemscontrol, it tries to bind back to the form again before creating a new empty form.
The binding back sets the radio button true say if true is selected but then setting it to trus starts a tennis match between the two converters one converts it the other converts it back and the model says No way the value is true and so on it goes until application hits stackoverflow...
Interesting situation I have hit into with WPF and MVVM pattern.I am looking for a solution without breaking the MVVM paradigm. If converters can be done away with that will work too.
The backend calls are CSLA reistered properties.
Thanks
Dhiren
You can bind only the Yes radio. Since it's IsChecked property is bound to your bool, the bool would turn to false when you check the other radio. You don't even need the ReverseConverter here.
UPDATE:
Only bind the Yes radio to your field. RadioButton are mutually exclusive when they are in the same group (which they are). When you select one, you change the other. If you select the No radio, yes would be unchecked, and so Required would be false.
As for the initial values, if you set No to false, it would be selected. And since Yes is bound to Required, it'll save your value. Do something like this:
<RadioButton Content="Yes" GroupName="GRequired" IsChecked="{Binding Required}"/>
<RadioButton Content="No" GroupName="GRequired" IsChecked="False"/>
UPDATE II:
The binding mechanism in WPF ties two values together, so when one changes the other does too, and it can work both ways. RadioButton.IsCheck is just a bool property, so when it's value change it changes the value it's bound to (in this case Required). When you check No, you also uncheck Yes, it's IsChecked property changes. and that changes Required.
For more on Data binding see: http://msdn.microsoft.com/en-us/library/ms752347.aspx.
So, you see, you don't need the ReverseConverter, because you don't need to bind No to anything. As a matter of fact, you don't even need NullBooleanConverter, since WPF already knows how to convert bool? to bool.
Regards,
Yoni

Override style of a specific dataItem

I am working on my first Windows 8 RT application and started with the sample grid application. My goal is to change the style of one of these grid items.
I have been able to 'find' this item by using this:
if (dataItem.UniqueId.StartsWith("Group-1-Item-1"))
Then I created this style in C# (just as an example);
Style style = new Style (typeof(SampleDataItem));
Thickness thick = new Thickness(4,4,4,4);
style.Setters.Add(new Setter(Border.BorderThicknessProperty, new Thickness(100)));
However, now I have to apply this style to the specific data item - I have tried a lot of things, but I don't take they make much sense now that I look at them.
The data template for the dataItems for the Grid App project template is found in StandardStyles.xaml, and is referred to by the key "Standard250x250ItemTemplate".
If the idea is to vary the styling based on the content of the item, one approach might be to use a Binding.Converter, as shown here, to convert the value of the content in question to the desired style or style property.
For example, If I wanted to make any items in the default Grid App template have a green background if the item number was less than or equal to 3, and red if it was higher than 3, I would create the following converter class (in my case, I just added the class to the GroupedItemsPage.xaml.cs, just before the GroupedItemsPage partial class):
// Custom class implements the IValueConverter interface.
public class StyleConverter : IValueConverter
{
#region IValueConverter Members
// Define the Convert method to change a title ending with > 3
// to a red background, otherwise, green.
public object Convert(object value, Type targetType,
object parameter, string language)
{
// The value parameter is the data from the source object.
string title = (string)value;
int lastNum = (int.Parse(title.Substring(title.Length - 1)));
if (lastNum > 3)
{
return "Red";
}
else
{
return "Green";
}
}
// ConvertBack is not implemented for a OneWay binding.
public object ConvertBack(object value, Type targetType,
object parameter, string language)
{
throw new NotImplementedException();
}
#endregion
}
Once the class is defined, along with the desired Convert method, I add an instance to it in my App.xaml file, so it will be available to the item template in StandardStyles.xaml:
<!-- Application-specific resources -->
<local:StyleConverter x:Key="StyleConverter"/>
In StandardStyles.xaml, I made a copy of the Standard250x250ItemTemplate that binds from the Title property of the item, to my converter class, like so:
<DataTemplate x:Key="Standard250x250ItemTemplate_Convert">
<Grid HorizontalAlignment="Left" Width="250" Height="250">
<Border Background="{Binding Title,
Converter={StaticResource StyleConverter}}">
</Border>
<StackPanel VerticalAlignment="Bottom"
Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
<TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
<TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
</StackPanel>
</Grid>
</DataTemplate>
the key piece being the binding of the Border element's Background property to the Title, with the Converter={StaticResource StyleConverter}, which wires the item template up to my converter. Note that in addition to binding the Background property, I also removed the nested Image element from the original version of the item template.
Finally, in GroupedItemsPage.xaml, I change the item template to my customized version:
<!-- Horizontal scrolling grid used in most view states -->
<GridView
x:Name="itemGridView"
...
Padding="116,137,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard250x250ItemTemplate_Convert}"
...
ItemClick="ItemView_ItemClick">
Once that's done, I can run the project, and here's what I see:
Hope that helps!
For more info on Windows Store app development, register for Generation App.

IValueConverter not getting invoked in some scenarios

I am using a collection of texts fetched from a web service, which should be used for a variety of controls.
The easiest and most dynamic way to do this, in my opinion, is to use an IValueConverter to get the given text as follows:
public class StaticTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (parameter != null && parameter is string)
{
return App.StaticTexts.Items.SingleOrDefault(t => t.Name.Equals(parameter)).Content;
}
return null;
}
}
And then in the XAML I give the ID of the text ('Name') to the converter:
<phone:PhoneApplicationPage.Resources>
<Helpers:StaticTextConverter x:Name="TextConverter" />
</phone:PhoneApplicationPage.Resources>
<TextBlock Text="{Binding Converter={StaticResource TextConverter}, ConverterParameter=M62}" />
Then to change the text of some control, all that has to be done is to either change the ID in the parameter or change the text itself from some web interface.
My problem is
That the value converter only gets invoked when in some sort of DataTemplate context where the ItemSource has been set, as if the Binding property only works there.
Whenever I use this method anywhere else, the value converter is simply not invoked.
Does anyone have an idea of what I might be doing wrong?
Set DataContext="object" for your textblocks where the convertet is not working and the value converter will be invoked.
This workaround will do the trick in your scenario.
add source for the binding
use something like that
Text="{Binding Converter={StaticResource LocalizedStringsConventer} ,ConverterParameter=Wrong, Source=NULL}"

Binding to instance of ItemsSource

I have a ListBox on a WPF User Control that is defined as
<ListBox Grid.Row="1" ScrollViewer.CanContentScroll="False" Background="#00000000" BorderThickness="0" ItemsSource="{Binding BuyItNowOptions}"
ItemTemplate="{DynamicResource BuyItNowDataTemplate}" IsSynchronizedWithCurrentItem="True"
Style="{DynamicResource InheritEmptyListStyle}" SelectedItem="{Binding SelectedResearch}" ItemContainerStyle="{DynamicResource ListBoxItemStyle}"/>
BuyItNowOptions is a public property on the ViewModel that is of type ObservableCollection
In the BuyItNowDataTemplate I have a label that needs to have some logic performed before displaying a price.
<Label Padding="1" HorizontalContentAlignment="Stretch" Grid.Column="2" Grid.Row="2" Margin="1">
<TextBlock Text="{Binding ExchangePrice, StringFormat=C}"
Visibility="{Binding ReturnRequired, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Label>
The binding here indicates that it will use the ExchangePrice property of the instance of AutoResearchProxy that it is on like BuyItNowOptions[CurrentIndex].ExchangePrice.
What I would like to know is it possible to create the binding in such a way that it references the whole instance of the AutoResearchProxy so that I can pass it to a converter and manipulate several properties of the AutoResearchProxy and return a calculated price?
I would envision my converter looking like this.
public class PriceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is AutoResearchProxy)
{
var research = value as AutoResearchProxy;
//Some logic to figure out actual price
}
else
return String.Empty;
}
Hopefully this makes sense.
You can pass the whole datacontext-object to a Binding by not specifying a Path or by setting it to ., that however will result in the binding not updating if any of the relevant properties of that object change.
I would recommend you use a MultiBinding instead, that way you can target the necessary properties and the binding will update if any of those change. (For usage examples see the respective section on MSDN)
MyProperty="{Binding Converter={StaticResource ObjectToDerivedValueConverter}
That should do it.

Categories