Visibility converter doesn't work even though binding works - c#

I am trying to use the visibility converter from MvvmCross but for some reason the visibility of the view that I'm trying to edit doesn't change at all.
I am using fluent binding in order to bind the element:
var set = this.CreateBindingSet<NextStopFragment, NextStopViewModel>();
set.Bind(Header).For(v => v.Visibility).To(vm => vm.LayoutVisibility_0).WithConversion("Visibility");
set.Apply();
And this is the code from my viewmodel:
private bool _layoutVisibility_0 = false;
public bool LayoutVisibility_0
{
get { return _layoutVisibility_0; }
set { _layoutVisibility_0 = value; RaisePropertyChanged(() => LayoutVisibility_0); }
}
public override void Prepare(Parameter parameter)
{
LayoutVisibility_0 = true;
}
This is the layout of the view that I'm binding (please note that I am restricted to using fluent binding on this):
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/NextStopHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#drawable/border_bottom_gray"
android:paddingLeft="15dp"
android:paddingRight="15dp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:visibility="gone"/>
For the moment I am only trying to make the LinearLayout visible from the start, but it doesn't work. The binding works (I think) because the property gets updated correctly every time I am trying to edit it, but even if the property is set to true, the view remains "gone".
Have I missed something? Do I need to add anything else? Please note that this is ALL the code that I'm using for editing the visibility.

Related

Xamarin Android TabLayout Badge: Bind Text Mvvmcross

I made a Custom Layout to add a badge to a tab on Android. This badge is a TextView.
How do I bind this component?
It's possible?
My custom badge layout:
<LinearLayout
android:id="#+id/badgeCotainer"
android:layout_width="wrap_content"
android:layout_height="20dp"
android:layout_marginStart="90dp"
android:background="#drawable/notifications_background"
android:gravity="center"
android:layout_gravity="right"
android:minWidth="16dp">
<TextView
android:id="#+id/badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAlignment="center"
android:textColor="#011f7a"
android:textSize="10sp"
android:text="0"
app:MvxBind="Text ContMaterial"/>
</LinearLayout>
My Activity: use MvxViewPagerFragmentInfo
private void InitTabs()
{
var viewPager = FindViewById<ViewPager>(Resource.Id.materiais_viewpager);
if (viewPager != null)
{
var fragments = new List<MvxViewPagerFragmentInfo>
{
new MvxViewPagerFragmentInfo("Requisição",
typeof(CadastraRequisicaoFragment), ViewModel),
new MvxViewPagerFragmentInfo("Materiais",
typeof(ListaMateriaisFragment), ViewModel),
};
viewPager.Adapter = new MvxCachingFragmentStatePagerAdapter(this, SupportFragmentManager, fragments);
}
var tabLayout = FindViewById<TabLayout>(Resource.Id.requisicao_tabs);
tabLayout.SetupWithViewPager(viewPager);
tabLayout.GetTabAt(1).SetCustomView(Resource.Layout.tab_header_badge);
}
On view Model I used raisedPropertyChanged()
Badge appears normally, just don't change value.
Tablayout
Out of the box there is nothing in mvvmcross to bind the content of a TabLayout when you provide a custom view. You could however try something custom using MvxFrameControl
Basically, replace your LinearLayout by the MvxFrameControl, then after
tabLayout.SetupWithViewPager(viewPager);
inflate your custom view using the BindingInflate method, and set a value to your MvxFrameControl's BindingContext, then set your custom tablayout view with the result that operation.
That might do the trick.
Another way to do it, is to listen to your ViewModel's "PropertyChanged" events in your Activity, and manually make changes to your tab layouts when it applies.

MvvmCross : Empty binding target passed to MvxTargetBindingFactoryRegistry

I created an Android application with Xamarin and MvvmCross.
I want to bind some views (text, editing text, button) to my ViewModel. Nothing strange well so far. But my bindings don’t apply… When I use typed FindViewById, I don’t get the traced error but bindings doesn’t apply.
When I run the application, I have the following trace:
MvxBind:Error: Empty binding target passed to MvxTargetBindingFactoryRegistry
MvxBind:Warning: Failed to create target binding for binding for TextProperty
My override of OnCreate(Bundle bundle) void is :
SetContentView(Resource.Layout.Reference);
var referenceTextView = FindViewById(Resource.Id.referenceEditView); // untyped FindViewById
var siteTextView = FindViewById<TextView>(Resource.Id.siteTextView); // typed FindViewById<T>
//var goButton = FindViewById<Button>(Resource.Id.goButton);
var bindingsSet = this.CreateBindingSet<ReferenceView, ReferenceViewModel>();
bindingsSet.Bind(referenceTextView).To(vm => vm.Reference).Mode(MvxBindingMode.TwoWay);
bindingsSet.Bind(siteTextView).To(vm => vm.Site);
//bindingsSet.Bind(goButton).To(vm => vm.GoCommand);
bindingsSet.Apply();
base.OnCreate(bundle);
I’ve tried to do in the AXML :
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/siteTextView"
android:text="####"
local:MvxBind="Text Site"
android:gravity="center" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/referenceTextView"
android:hint="Numéro de dossier"
local:MvxBind="Text Reference" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Accéder"
android:id="#+id/goButton"
local:MvxBind="Click GoCommand" />
The getters and setters of my properties use RaiseAndSetIfChanged method :
private string _reference;
public string Reference
{
get { return _reference; }
set { this.RaiseAndSetIfChanged(ref _reference, value, () => Reference); }
}
I’ve the same LinkerPleaseInclude class than LinkerPleaseInclude original class.
My setup inherits from MvxAndroidSetup class
And on other ViewModels, the bindings are applied correctly.
You need to call base.OnCreate(bundle); before SetContentView as the ViewModel is located and attached inside of that call. Failing to do so will obviously give you the exact errors you see. Source will be null and will not bind to the target.
So you can either do:
base.OnCreate(bundle);
SetContentView(Resource.Layout.Reference);
And have all your bindings in your AXML. Or you can do the other approach setting the bindings behind the scenes:
base.OnCreate(bundle);
SetContentView(Resource.Layout.Reference);
var referenceTextView = FindViewById<TextView>(Resource.Id.referenceEditView);
var siteTextView = FindViewById<TextView>(Resource.Id.siteTextView);
var bset = this.CreateBindingSet<ReferenceView, ReferenceViewModel>();
bset.Bind(referenceTextView).To(vm => vm.Reference);
bset.Bind(siteTextView).To(vm => vm.Site);
bset.Apply();
Just make sure to call base.OnCreate to begin with.
The warnings
MvxBind:Error: 2,20 Empty binding target passed to MvxTargetBindingFactoryRegistry
MvxBind:Warning: 2,20 Failed to create target binding for binding for Text
are caused by var referenceTextView = FindViewById(Resource.Id.referenceEditView); resulting in referenceTextView to be of type View.
MvvmCross is searching for the default binding target property of the type TTarget when calling Bind<TTArget> without For(targetProperty). This is just a look up in a table like:
TTarget Property
----------------------
TextView Text
Button Click
... ...
In your case TTarget is View instead of TextView, because you pass it into bindingsSet.Bind(referenceTextView) wich is the implicit call of bindings.Bind<View>(btnNumber). View has no default binding target property. You have to set it explicitly like
bindings.Bind(btnNumber).For("Text")
or use the typed FindViewById<TextView>.
I dont think you need to bind twice, remove these lines:
var referenceTextView = FindViewById(Resource.Id.referenceEditView); // untyped FindViewById
var siteTextView = FindViewById<TextView>(Resource.Id.siteTextView); // typed FindViewById<T>
//var goButton = FindViewById<Button>(Resource.Id.goButton);
var bindingsSet = this.CreateBindingSet<ReferenceView, ReferenceViewModel>();
bindingsSet.Bind(referenceTextView).To(vm => vm.Reference).Mode(MvxBindingMode.TwoWay);
bindingsSet.Bind(siteTextView).To(vm => vm.Site);
//bindingsSet.Bind(goButton).To(vm => vm.GoCommand);
bindingsSet.Apply();
So your on create is just this:
SetContentView(Resource.Layout.Reference);
base.OnCreate(bundle);
And keep the bindings in the axml file.
Make sure you have this at the top of your xaml file:
xmlns:local="http://schemas.android.com/apk/res-auto"
Also if you are doing bindings in the cs file, the MvvmCross binding mode is TwoWay by default. So you dont need .Mode(MvxBindingMode.TwoWay);

Load CheckedTextView items in ListView with checked states set

When my activity starts and I initially load my ListView with items, I want to be able to set some of those items to checked however that doesn't seem to be working.
I'm using the simple_list_item_multiple_choice layout so the items are CheckedTextView.
Here's the adapter. Text displays properly however the Checked property doesn't seem to be doing anything visually despite it being set to true/false.
public class VehicleSubListAdapter : BaseAdapter<string>
{
// ...
public override View GetView(int position, View convertView, ViewGroup parent)
{
View view = convertView ?? _context.LayoutInflater.Inflate(Android.Resource.Layout.SimpleListItemMultipleChoice, parent, false);
var checkedTextView = view.FindViewById<CheckedTextView>(Android.Resource.Id.Text1);
checkedTextView.Text = _items[position].Name;
// This doesn't seem to do anything
checkedTextView.Checked = _items[position].Checked;
// I've even tried this with no change visually
//checkedTextView.Checked = true;
return view;
}
}
Here's the activity which can be closed and re-opened but needs to resume from where it left off (some items checked, some items not). That is why things are stored in a global static variable.
If the global list is empty, manually update the lists. Else, load from the global list. It successfully loads from the global list and successfully stores the Checked property of that list as modified in the item click event. However, the checked items aren't shown as checked when the activity loads.
Also I've tried putting the call to FillList() in both OnCreate and OnResume and neither have worked.
public class VehicleConditionSubActivity : ListActivity
{
private List<VehicleConditionItemDTO> _items;
private VehicleSubListAdapter _vehicleAdapter;
//...
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
SetTheme(Resource.Style.MyAppTheme);
SetContentView(Resource.Layout.VehicleConditionItem);
// Set up action bar
ActionBar.SetDisplayHomeAsUpEnabled(true);
// Set up list view
ListView.ChoiceMode = ChoiceMode.Multiple;
_items = new List<VehicleConditionItemDTO>();
_vehicleAdapter = new VehicleSubListAdapter(this, _items);
ListAdapter = _vehicleAdapter;
ListView.ItemClick += ListView_ItemClick;
//...
// Fill list view
FillList();
}
private void FillList()
{
AddFluidItems();
_vehicleAdapter.NotifyDataSetChanged();
}
private void AddFluidItems()
{
// I've checked that this is being called correctly
if (Global.Conditions.FluidLevels.Count > 0)
{
foreach (var item in Global.Conditions.FluidLevels)
{
_items.Add(item);
}
}
else
{
_items.Add(new VehicleConditionItemDTO("1", "Engine oil"));
// ...
foreach (var item in _items)
{
Global.Conditions.FluidLevels.Add(item);
}
}
}
// ...
private void ListView_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
// Items already check/uncheck properly when tapped
// So I just update the global list items checked property
Global.Conditions.FluidLevels[e.Position].Checked = !Global.Conditions.FluidLevels[e.Position].Checked;
}
}
The layout for the activity is
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:minWidth="25px"
android:minHeight="25px"
android:background="#color/background">
<ListView
android:minWidth="25px"
android:minHeight="25px"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="#android:id/list" />
</LinearLayout>
Selecting Items Programmatically
Manually setting which items are ‘selected’ is done with the
SetItemChecked method (it can be called multiple times for multiple
selection)
Customizing A List View's Appearance
Note that SetItemChecked is on the ListView.
You can remove the code in GetView that sets Checked.
Then, after (or during the call to FillList) for each item that should be initially checked, call:
ListView.SetItemChecked (position, true);

Binding to checked propert messes up ui, binding to text is fine

UPDATE:
I think i have found the problem.
All my forms events that affect the binding source have this in the end:
BndSource.ResetBindings(false);
If i comment this line in my CheckedChanged event handler, the issue stops. But why?
I have a very strange bug.
I have a class property:
public SqlByte AutomaticFlag { get; set; }
I wanted to use checkbox to facilitate for showing this so in initial inding i do this:
dtaAutomaticFlag.DataBindings.Add("Checked", BndSource, "AutomaticFlag", true);
dtaAutomaticFlag.DataBindings[0].Format += (s, e) =>
{
if ((SqlByte)e.Value == 1)
{
e.Value = true;
}
else
{
e.Value = false;
}
};
the problem is that during iteration through all records of the binding source my ui is half updated, meaning its not complete. See picture:
VERY strangely when i change the above binding property from checked to text like this:
dtaAutomaticFlag.DataBindings.Add("Text", BndSource, "AutomaticFlag", true);
the ui is ok!!
Picture:
I'm not sure if this applies to this particular situation. But rather than adding the binding as you did:
dtaAutomaticFlag.DataBindings.Add("Text", BndSource, "AutomaticFlag", true);
Does creating a "new" binding instance help at all?
dtaAutomaticFlag.DataBindings.Add(new Binding("Text", BndSource, "AutomaticFlag", true));

Binding a UserControl to a custom BusyIndicator control

I have a requirement to focus on a specific textbox when a new view is loaded.
The solution was to add this line of code to the OnLoaded event for the view:
Dispatcher.BeginInvoke(() => { NameTextBox.Focus(); });
So this worked for one view, but not another. I spent some time debugging the problem and realized that the new view I was working on had a BusyIndicator that takes focus away from all controls since the BusyIndicator being set to true and false was occuring after the OnLoaded event.
So the solution is to call focus to the NameTextBox after my BusyIndicator has been set to false. My idea was to create a reusable BusyIndicator control that handles this extra work. However, I am having trouble doing this in MVVM.
I started by making a simple extension of the toolkit:BusyIndicator:
public class EnhancedBusyIndicator : BusyIndicator
{
public UserControl ControlToFocusOn { get; set; }
private bool _remoteFocusIsEnabled = false;
public bool RemoteFocusIsEnabled
{
get
{
return _remoteFocusIsEnabled;
}
set
{
if (value == true)
EnableRemoteFocus();
}
}
private void EnableRemoteFocus()
{
if (ControlToFocusOn.IsNotNull())
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
else
throw new InvalidOperationException("ControlToFocusOn has not been set.");
}
I added the control to my XAML file with no problem:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
RemoteFocusIsEnabled="{Binding IsRemoteFocusEnabled}"
IsBusy="{Binding IsDetailsBusyIndicatorActive}"
...
>
...
<my:myTextBox (this extends TextBox)
x:Name="NameTextBox"
...
/>
...
</my:EnhancedBusyIndicator>
So the idea is when IsRemoteFocusEnabled is set to true in my ViewModel (which I do after I've set IsBusy to false in the ViewModel), focus will be set to NameTextBox. And if it works, others could use the EnhancedBusyIndicator and just bind to a different control and enable the focus appropriately in their own ViewModels, assuming their views have an intial BusyIndicator active.
However, I get this exception when the view is loaded:
Set property 'foo.Controls.EnhancedBusyIndicator.ControlToFocusOn' threw an exception. [Line: 45 Position: 26]
Will this solution I am attempting work? If so, what is wrong with what I have thus far (cannot set the ControlToFocusOn property)?
Update 1
I installed Visual Studio 10 Tools for Silverlight 5 and got a better error message when navigating to the new view. Now I gete this error message:
"System.ArgumentException: Object of type System.Windows.Data.Binding cannot be converted to type System.Windows.Controls.UserControl"
Also, I think I need to change the DataContext for this control. In the code-behind constructor, DataContext is set to my ViewModel. I tried adding a DataContext property to the EnhancedBusyIndicator, but that did not work:
<my:EnhancedBusyIndicator
DataContext="{Binding RelativeSource={RelativeSource Self}}"
ControlToFocusOn="{Binding ElementName=NameTextBox}"
RemoteFocusIsEnabled="{Binding IsRemoteFocusEnabled}"
IsBusy="{Binding IsDetailsBusyIndicatorActive}"
...
>
Update 2
I need to change UserControl to Control since I will be wanting to set focus to TextBox objects (which implement Control). However, this does not solve the issue.
#Matt, not sure
DataContext="{Binding RelativeSource={RelativeSource Self}}"
will work in Silverlight 5, have you tried binding it as a static resource?
Without a BusyIndicator present in the view, the common solution to solve the focus problem is to add the code
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
to the Loaded event of the view. This actually works even with the BusyIndicator present; however, the BusyIndicator immediately takes focus away from the rest of the Silverlight controls. The solution is to invoke the Focus() method of the control after the BusyIndicator is not busy.
I was able to solve it by making a control like this:
public class EnhancedBusyIndicator : BusyIndicator
{
public EnhancedBusyIndicator()
{
Loaded += new RoutedEventHandler(EnhancedBusyIndicator_Loaded);
}
void EnhancedBusyIndicator_Loaded(object sender, RoutedEventArgs e)
{
AllowedToFocus = true;
}
private readonly DependencyProperty AllowedToFocusProperty = DependencyProperty.Register("AllowedToFocus", typeof(bool), typeof(EnhancedBusyIndicator), new PropertyMetadata(true));
public bool AllowedToFocus
{
get { return (bool)GetValue(AllowedToFocusProperty); }
set { SetValue(AllowedToFocusProperty, value); }
}
public readonly DependencyProperty ControlToFocusOnProperty = DependencyProperty.Register("ControlToFocusOn", typeof(Control), typeof(EnhancedBusyIndicator), null);
public Control ControlToFocusOn
{
get { return (Control)GetValue(ControlToFocusOnProperty); }
set { SetValue(ControlToFocusOnProperty, value); }
}
protected override void OnIsBusyChanged(DependencyPropertyChangedEventArgs e)
{
base.OnIsBusyChanged(e);
if (AllowedToFocus && !IsBusy)
{
Dispatcher.BeginInvoke(() => { ControlToFocusOn.Focus(); });
AllowedToFocus = false;
}
}
}
To use it, replace the BusyIndicator tags in your xaml with the new EnhancedBusyIndicator and add the appropriate namespace.
Add a new property, ControlToFocusOn inside the element, and bind it to an existing element in the view that you want focus to be on after the EnhancedBusyIndicator disappears:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
...
>
...
</my:EnhancedBusyIndicator>
In this case, I focused to a textbox called NameTextBox.
That's it. This control will get focus every time we navigate to the page. While we are on the page, if the EnhancedBusyIndicator becomes busy and not busy agiain, focus will not go to the control; this only happens on initial load.
If you want to allow the EnhancedBusyIndicator to focus to the ControlToFocusOn another time, add another property, AllowedToFocus:
<my:EnhancedBusyIndicator
ControlToFocusOn="{Binding ElementName=NameTextBox}"
AllowedToFocus="{Binding IsAllowedToFocus}"
...
>
...
</my:EnhancedBusyIndicator>
When AllowedToFocus is set to true, the next time EnhancedBusyIndicator switches from busy to not busy, focus will go to ControlToFocusOn.
AllowedToFocus can also be set to false when loading the view, to prevent focus from going to a control. If you bind AllowedToFocus to a ViewModel property, you may need to change the BindingMode. By default, it is OneTime.

Categories