Xamarin XAML to C# Data Trigger - c#

In reference to this question below I have XAML working, but am struggling with converting to C#
Xamarin / MAUI XAML to C#
Here is my attempt that isn't working and I'm not sure why... My goal is when LayoutState.Success it updated via VenuePageViewModel, VenueSuccessContentView should display inside the cvWrap ContentView on the Page.
public partial class VenuePageViewModel : ObservableObject
{
[ObservableProperty]
private LayoutState layoutState = LayoutState.Loading;
public VenuePageViewModel()
{
LayoutState = LayoutState.Success;
}
}
public class VenueSuccessContentView : ContentView
{
public VenueSuccessContentView()
{
Content = new Label() { Text = "hello world", TextColor = Colors.Red };
}
}
public class MainPageCS : ContentPage
{
public MainPageCS()
{
BindingContext = new VenuePageViewModel();
var venueSuccessCV = new VenueSuccessContentView();
Resources.Add(nameof(LayoutState.Success), venueSuccessCV);
var cvWrap = new ContentView();
var cv = new ContentView();
cvWrap.Content = cv;
Content = cvWrap;
var datatrigger = new DataTrigger(typeof(ContentView))
{
Binding = new Binding(source: RelativeBindingSource.TemplatedParent, path: nameof(VenuePageViewModel.LayoutState)),
Value = LayoutState.Success,
Setters = {
new Setter { Property = ContentView.ContentProperty, Value = venueSuccessCV },
}
};
cvWrap.Triggers.Add(datatrigger);
}
}
Minimal Repo: https://github.com/aherrick/DataTriggerCSharpMaui

The binding path is incorrect in DataTrigger .
Modify Binding property of DataTrigger as below .
Binding = new Binding( path: nameof(VenuePageViewModel.LayoutState))

Related

Format string is ignored after TextBox gets bound to a property

In a dynamically built UserControl I have set the format string for a TextBox this way:
TextBox newTextBox = new TextBox();
TempViewModel viewModel = new TempViewModel();
var binding = new System.Windows.Data.Binding
{
Source = viewModel,
Path = new PropertyPath("DecimalValue"),
ConverterCulture = new System.Globalization.CultureInfo("de-DE"),
StringFormat = "{0:#,##0.00}"
};
newTextBox.SetBinding(TextBox.TextProperty, binding);
public class TempViewModel
{
public decimal DecimalValue { get; set; }
}
That works fine so far.
But after adding a DependencyProperty the format is ignored. The Dependencyproperty is defined this way:
public class UserControl_CBOGridQuotePositions : UserControl
{
private static readonly DependencyProperty Amount_QuotePos_Base_DependencyProperty =
DependencyProperty.Register("Amount_QuotePos_Base", typeof(System.Decimal), typeof(UserControl_CBOGridQuotePositions), new PropertyMetadata(0m));
public System.Decimal Amount_QuotePos_Base
{
get { return (System.Decimal)GetValue(UserControl_CBOGridQuotePositions.Amount_QuotePos_Base_DependencyProperty); }
set { SetValue(UserControl_CBOGridQuotePositions.Amount_QuotePos_Base_DependencyProperty, value); }
}
private void MakeTheBindings(DependencyProperty dependencyProperty)
{
var binding = new Binding("Amount_QuotePos_Base");
binding.Source = this; // which is the UserControl_CBOGridQuotePositions
newTextBox.SetBinding(dependencyProperty, binding);
}
}
Is there a way to make the format working while the TextBox is bound to a property?
Because in MakeTheBindings() you are replacing your Binding with a new one. Make sure when you do this var binding = new Binding("Amount_QuotePos_Base"); that you also set all the properties such as ConverterCulture and StringFormat

How to acces object inside CarouselView Xamarin.Forms

I have a Xamarin.Forms app that displays a ViewFlipper (https://github.com/TorbenK/ViewFlipper) inside a CarouselView.
I would like the ViewFlipper to flip back to the front when changing pages inside the carousel. But I can't seem to figure out how to access the ViewFlipper.
I have the following working code:
public class CarouselContent
{
public string FrontImg { get; set; }
public string BackImg { get; set; }
}
public class MainPage : ContentPage
{
public MainPage()
{
var pages = new ObservableCollection<CarouselContent>();
var page1 = new CarouselContent();
page1.FrontImg = "page1Front";
page1.BackImg = "page1Back";
var page2 = new CarouselContent();
page2.FrontImg = "page2Front";
page2.BackImg = "page2Back";
pages.Add(page1);
pages.Add(page2);
var carouselView = new Carousel(pages);
Content = carouselView;
}
}
public class Carousel : AbsoluteLayout
{
private DotButtonsLayout dotLayout;
private CarouselView carousel;
public Carousel(ObservableCollection<CarouselContent> pages)
{
carousel = new CarouselView();
var template = new DataTemplate(() =>
{
//create page
var absLayout = new AbsoluteLayout();
//create images for the flipper
var frontImg = new Image
{
Aspect = Aspect.AspectFit
};
frontImg.SetBinding(Image.SourceProperty, "FrontImg");
var backImg = new Image
{
Aspect = Aspect.AspectFit
};
backImg.SetBinding(Image.SourceProperty, "BackImg");
//create flipper
var flipper = new ViewFlipper.FormsPlugin.Abstractions.ViewFlipper();
flipper.FrontView = frontImg;
flipper.BackView = backImg;
//Add flipper to page
absLayout.Children.Add(flipper);
return absLayout;
});
carousel.ItemsSource = pages;
carousel.ItemTemplate = template;
Children.Add(carousel);
}
}
I tried adding the ViewFlipper to the CarouselContent but I couldn't get that to work. Any ideas?
EDIT:
I tried creating an AbsoluteLayout with bindable items and bind the items created in CarouselContent in the datatemplate of the CarouselView, but the line '(b as BindableAbsLayout).Children.Add((View)v);' in BindableAbsLayout is never called. What am I doing wrong?
class BindableAbsLayout : AbsoluteLayout
{
public static readonly BindableProperty ItemsProperty =
BindableProperty.Create(nameof(Items), typeof(ObservableCollection<View>), typeof(BindableAbsLayout), null,
propertyChanged: (b, o, n) =>
{
(n as ObservableCollection<View>).CollectionChanged += (coll, arg) =>
{
switch (arg.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var v in arg.NewItems)
(b as BindableAbsLayout).Children.Add((View)v);
break;
case NotifyCollectionChangedAction.Remove:
foreach (var v in arg.NewItems)
(b as BindableAbsLayout).Children.Remove((View)v);
break;
}
};
});
public ObservableCollection<View> Items
{
get { return (ObservableCollection<View>)GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
}
public class CarouselContent
{
private ViewFlipper.FormsPlugin.Abstractions.ViewFlipper _flipper;
private ObservableCollection<View> _items;
public ObservableCollection<View> Items
{
get { return _items; }
}
public CarouselContent(string frontImgStr, string backImgStr)
{
_items = new ObservableCollection<View>();
_flipper = new ViewFlipper.FormsPlugin.Abstractions.ViewFlipper();
var frontImg = new Image
{
Aspect = Aspect.AspectFit
};
frontImg.Source = frontImgStr;
var backImg = new Image
{
Aspect = Aspect.AspectFit
};
backImg.Source = backImgStr;
_flipper.FrontView = frontImg;
_flipper.BackView = backImg;
AbsoluteLayout.SetLayoutBounds(_flipper, new Rectangle(0.5, 0.05, 0.85, 0.85));
AbsoluteLayout.SetLayoutFlags(_flipper, AbsoluteLayoutFlags.All);
Items.Add(_flipper);
}
}
public class Carousel : AbsoluteLayout
{
private DotButtonsLayout dotLayout;
private CarouselView carousel;
public Carousel(ObservableCollection<CarouselContent> pages)
{
carousel = new CarouselView();
var template = new DataTemplate(() =>
{
var absLayout = new BindableAbsLayout();
absLayout.BackgroundColor = Color.FromHex("#68BDE4");
absLayout.SetBinding(BindableAbsLayout.ItemsProperty,"Items");
return absLayout;
});
carousel.ItemsSource = pages;
carousel.ItemTemplate = template;
Children.Add(carousel);
}
}
Not sure what the best practice is here, but you could try accessing it via the ItemSelected Event (which fires every time you change back and forth in the carouselview)
Wire it up
carousel.ItemSelected += carouselOnItemSelected;
Get your ViewFlipper
private void carouselOnItemSelected(object sender, SelectedItemChangedEventArgs selectedItemChangedEventArgs)
{
CarouselContent carouselContent = selectedItemChangedEventArgs.SelectedItem;
ViewFlipper viewFlipper = carouselContent.Children[0];
viewFlipper.FlipState = ViewFlipper.FrontView;
}

Implementing Xamarin Forms context actions

I am trying to implement context actions on my list in Xamarin Forms but can't get it to work.
I am not using XAML, but instead creating my layout in code.
I am trying to follow the steps in https://developer.xamarin.com/guides/xamarin-forms/user-interface/listview/interactivity/#Context_Actions and I want to push a new page when "Edit" is clicked.
I cleaned up my code and removed my feeble attempts to make things work.
So this is my custom list cell:
public class PickerListCell : TextCell
{
public PickerListCell ()
{
var moreAction = new MenuItem { Text = App.Translate ("Edit") };
moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
moreAction.Clicked += async (sender, e) => {
var mi = ((MenuItem)sender);
var option = (PickerListPage.OptionListItem)mi.CommandParameter;
var recId = new Guid (option.Value);
// This is where I want to call a method declared in my page to be able to push a page to the Navigation stack
};
ContextActions.Add (moreAction);
}
}
And here is my model:
public class OptionListItem
{
public string Caption { get; set; }
public string Value { get; set; }
}
And this is the page:
public class PickerPage : ContentPage
{
ListView listView { get; set; }
public PickerPage (OptionListItem [] items)
{
listView = new ListView () ;
Content = new StackLayout {
Children = { listView }
};
var cell = new DataTemplate (typeof (PickerListCell));
cell.SetBinding (PickerListCell.TextProperty, "Caption");
cell.SetBinding (PickerListCell.CommandParameterProperty, "Value");
listView.ItemTemplate = cell;
listView.ItemsSource = items;
}
// This is the method I want to activate when the context action is called
void OnEditAction (object sender, EventArgs e)
{
var cell = (sender as Xamarin.Forms.MenuItem).BindingContext as PickerListCell;
await Navigation.PushAsync (new RecordEditPage (recId), true);
}
}
As you can see by my comments in the code, I have indicated where I believe things are missing.
Please assist guys!
Thanks!
Probably is too late for you, but can help others. The way i've found to do this is passing the instance of page on creation the ViewCell.
public class PickerListCell : TextCell
{
public PickerListCell (PickerPage myPage)
{
var moreAction = new MenuItem { Text = App.Translate ("Edit") };
moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
moreAction.Clicked += async (sender, e) => {
var mi = ((MenuItem)sender);
var option = (PickerListPage.OptionListItem)mi.CommandParameter;
var recId = new Guid (option.Value);
myPage.OnEditAction();
};
ContextActions.Add (moreAction);
}
}
So, in your page:
public class PickerPage : ContentPage
{
ListView listView { get; set; }
public PickerPage (OptionListItem [] items)
{
listView = new ListView () ;
Content = new StackLayout {
Children = { listView }
};
var cell = new DataTemplate(() => {return new PickerListCell(this); });
cell.SetBinding (PickerListCell.TextProperty, "Caption");
cell.SetBinding (PickerListCell.CommandParameterProperty, "Value");
listView.ItemTemplate = cell;
listView.ItemsSource = items;
}
void OnEditAction (object sender, EventArgs e)
{
var cell = (sender as Xamarin.Forms.MenuItem).BindingContext as PickerListCell;
await Navigation.PushAsync (new RecordEditPage (recId), true);
}
}
Ok, so with the help of some posts, specifically this one https://forums.xamarin.com/discussion/27881/best-practive-mvvm-navigation-when-command-is-not-available, I came to the following solution, though I'm not perfectly satisfied with the way it looks.
My custom cell now announces when a command is being executed using MessagingCenter:
public class PickerListCell : TextCell
{
public PickerListCell ()
{
var moreAction = new MenuItem { Text = App.Translate ("Edit") };
moreAction.SetBinding (MenuItem.CommandParameterProperty, new Binding ("."));
moreAction.Clicked += async (sender, e) => {
var mi = ((MenuItem)sender);
var option = (PickerListPage.OptionListItem)mi.CommandParameter;
var recId = new Guid (option.Value);
// HERE I send a request to open a new page. This looks a
// bit crappy with a magic string. It will be replaced with a constant or enum
MessagingCenter.Send<OptionListItem, Guid> (this, "PushPage", recId);
};
ContextActions.Add (moreAction);
}
}
And in my PickerPage constructor I added this subscription to the Messaging service:
MessagingCenter.Subscribe<OptionListItem, Guid> (this, "PushPage", (sender, recId) => {
Navigation.PushAsync (new RecordEditPage (recId), true);
});
All this works just find, but I'm not sure if this is the way it was intended to. I feel like the binding should be able to solve this without the Messaging Service, but I can't find out how to bind to a method on the page, only to a model, and I don't want to pollute my model with methods that have dependencies on XF.

Set Xamarin.Forms binding CommandParameter in code behind

I'm trying to set binding on a TapGestureRecognizer in code and I can't figure out the right way to do it. The working xaml looks something like this...
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding LaunchLocationDetailsCommand}"
CommandParameter="{Binding}" />
</Grid.GestureRecognizers>
</Grid>
And in C#, it looks something like this...
var gridTap = new TapGestureRecognizer();
gridTap.SetBinding(TapGestureRecognizer.CommandProperty,
new Binding("LaunchLocationDetailsCommand"));
gridTap.SetBinding(TapGestureRecognizer.CommandParameterProperty,
new Binding(/* here's where I'm confused */));
var grid = new Grid();
grid.GestureRecognizers.Add(gridTap);
My confusion comes in on the binding of CommandParameterProperty. In xaml, this simply {Binding} with no other parameter. How is this done in code? Passing in new Binding() or this.BindingContext don't seem to work.
The CommandProperty binding is the same as you was doing.
As you are not passing in a path to some property to use, your CommandParameterProperty can't just create an empty Binding as it will throw an exception.
To get around this you need to specify the Source property as Adam has pointed out.
Note, however if the BindingContext you are trying to assign is Null, which I suspect it is in your case, this will still throw an exception.
The Grid in the example below has the BindingContext set to the view model (objGrid.BindingContext = objMyView2).
We are then creating a new binding for our command parameter, with the Source pointing back to our view model (Source = objGrid.BindingContext).
If you run the demo below, you will see a debug message in the Output window indicating a property value from the view model.
MyView2 objMyView2 = new MyView2();
objMyView2.SomeProperty1 = "value1";
objMyView2.SomeProperty2 = "value2";
objMyView2.LaunchLocationDetailsCommand_WithParameters = new Command<object>((o2)=>
{
LaunchingCommands.LaunchLocationDetailsCommand_WithParameters(o2);
});
Grid objGrid = new Grid();
objGrid.BindingContext = objMyView2;
objGrid.HeightRequest = 200;
objGrid.BackgroundColor = Color.Red;
TapGestureRecognizer objTapGestureRecognizer = new TapGestureRecognizer();
objTapGestureRecognizer.SetBinding(TapGestureRecognizer.CommandProperty, new Binding("LaunchLocationDetailsCommand_WithParameters"));
Binding objBinding1 = new Binding()
{
Source = objGrid.BindingContext
};
objTapGestureRecognizer.SetBinding(TapGestureRecognizer.CommandParameterProperty, objBinding1);
//
objGrid.GestureRecognizers.Add(objTapGestureRecognizer);
Supporting classes:-
MyView2:-
public class MyView2
: ViewModelBase
{
public string SomeProperty1 { get; set; }
public string SomeProperty2 { get; set; }
public ICommand LaunchLocationDetailsCommand_WithParameters { get; set; }
}
LaunchingCommands:-
public static class LaunchingCommands
{
public static void LaunchLocationDetailsCommand_WithParameters(object pobjObject)
{
System.Diagnostics.Debug.WriteLine("SomeProperty1 = " + (pobjObject as MyView2).SomeProperty1);
}
}
ViewModelBase:-
public abstract class ViewModelBase
: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string pstrPropertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pstrPropertyName));
}
}
}
If you have a {Binding} with nothing inside it, it is binding to the binding context and passing that through. Hence you bind it to the default binding context of the page.
When you create a new Binding set the source.
var binding = new Xamarin.Forms.Binding() { Source = this.BindingContext };

How to create a custom property editor for Extended WPF Toolkit PropertyGrid with another (nested) editor in C#?

I have following generic class:
public class Member<T>
{
public bool IsDirty { get; set; }
public T Value { get; set; }
}
I want to create a custom editor for the PropertyGrid that will allow me to edit the IsDirty property through a CheckBox and the Value property through another nested editor.
With help I found here I've got this far:
class MemberEditor<T, TEditor> : ITypeEditor where TEditor : ITypeEditor
{
public FrameworkElement ResolveEditor(PropertyItem propertyItem)
{
//var member = propertyItem.Value as Member<T>;
// checkbox for the Member.IsDirty value
var isDirtyCheckbox = new CheckBox();
var isDirtyBinding = new Binding("Value.IsDirty");
isDirtyBinding.Source = propertyItem;
isDirtyBinding.ValidatesOnDataErrors = true;
isDirtyBinding.ValidatesOnExceptions = true;
isDirtyBinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(isDirtyCheckbox, CheckBox.IsCheckedProperty, isDirtyBinding);
// inner editor
var valueEditor = new TextBox();
var valueBinding = new Binding("Value.Value");
valueBinding.Source = propertyItem;
valueBinding.ValidatesOnExceptions = true;
valueBinding.ValidatesOnDataErrors = true;
valueBinding.Mode = BindingMode.TwoWay;
BindingOperations.SetBinding(valueEditor, TextBox.TextProperty, valueBinding);
// compose the editor
var dockPanel = new DockPanel();
DockPanel.SetDock(isDirtyCheckbox, Dock.Left);
dockPanel.Children.Add(isDirtyCheckbox);
DockPanel.SetDock(valueEditor, Dock.Right);
dockPanel.Children.Add(valueEditor);
return dockPanel;
}
}
Now I am looking for a way to replace the TextBox, for something like this:
// ...
TEditor editorResolver;
PropertyItem innerPropertyItem;
// ... magic happens here ...
FrameworkElement valueEditor = editorResolver.ResolveEditor(innerPropertyItem);
// ...
The main goal is to avoid creating new class for each nested editor type.
Any ideas will be very much appreciated!
Take a look at the solution that I provided in this SO question, where I provide a custom editor via a button and a separate window.

Categories

Resources