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 };
Related
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))
I'm trying to implement the XLabs CameraViewModel functionality into my Xamarin Forms App. Unfortunately, the given example uses XAML to bind the views with data, but i need to do it in code behind.
The following code is used to select a picture and get it's source.
public class CameraViewModel : XLabs.Forms.Mvvm.ViewModel
{
...
private ImageSource _imageSource;
private Command _selectPictureCommand;
public ImageSource ImageSource
{
get { return _imageSource; }
set { SetProperty(ref _imageSource, value); }
}
public Command SelectPictureCommand
{
get
{
return _selectPictureCommand ?? (_selectPictureCommand = new Command(
async () => await SelectPicture(),() => true));
}
}
...
}
And these commands are bound to XAML :
<Button Text="Select Image" Command="{Binding SelectPictureCommand}" />
<Image Source="{Binding ImageSource}" VerticalOptions="CenterAndExpand" />
How can I apply the same commands in code-behind for created elements?
CameraViewModel ViewModel = new CameraViewModel();
var Take_Button = new Button{ };
Take_Button.SetBindings(Button.CommandProperty, //*???*//);
var Source_Image = new Image { };
Source_Image.SetBinding(Image.SourceProperty, //*???*//);
I've successfully binded SelectPictureCommand by doing the following:
Take_Button .Command = ViewModel.SelectPictureCommand;
However I have my doubts about it being the correct way, and the same logic cannot be applies to ImageSource.
For the button you have:
var Take_Button = new Button{ };
Take_Button.SetBinding(Button.CommandProperty, new Binding { Path = nameof(ViewModel.SelectPictureCommand), Mode = BindingMode.TwoWay, Source = ViewModel});
For the image you have:
var Source_Image = new Image { };
Source_Image.SetBinding(Image.SourceProperty, new Binding { Path = nameof(ViewModel.ImageSource), Mode = BindingMode.TwoWay, Source = ViewModel });
I have 2 windows Parent window - Window_Products and Child window - Window_NewProduct
1)In my Window_Products
I have a list ObservableCollection ProductsList in this window which displays a list of products
AddNewProduct() is used to add new product to the list from child window
public AddNewProduct()
{
Window_NewProduct newProduct = new Window_NewProduct();
if(newProduct.ShowDialog() = true)
{
ProductsList.Add(//what code should I write here);
}
}
2)In my Window_NewProduct
This window uses a user control ProductUserControl since I use the user control as a Page as well as Window
<Window>
<local:ProductUserControl x:Name="ProductUserControl">
</Window>
3)In my product user control
public ProductUserControl()
{
this.DataContext = new ProductViewModel();
}
4)In my ProductViewModel
I have this object Product that stores the values like Prod_Name,Prod_Code in it.
What I want is this object Product to be returned to the parent window(Window_Products) after I save the product into database so that I can add new product to the observable collection above.
How can my object return back from the view model through the usercontrol,child window and then reach parent window.
Help me around this. Thanks in advance.
Make a new constructor for the indow_NewProduct:
public ProductUserControl(ProductViewModel model):base()
{
this.DataContext = model;
}
In your example :
1)In my Window_Products becomes:
var myPVM = new ProductViewModel();
Window_NewProduct newProduct = new Window_NewProduct(myPVM);
if(newProduct.ShowDialog() = true)
{
ProductsList.Add(myPVM.<THE NEW PRODUCT PROPERTY YOU WILL WRITE>);
}
A couple of things:
1. This is not good, but it may fit your needs:
2. Take a look at MVVM and MVC, combine them to also have controlers.
3. In WPF you should try as much as possible to use the DataContext to move your data arround, this NewProduct could be part of the prant's window data context.
Add a dependency property for your user control and bind to that in the xaml as below.
public static readonly DependencyProperty ProductProperty = DependencyProperty.Register(
"Product", typeof(ProductDto), typeof(ProductUserControl), new FrameworkPropertyMetadata(null));
public ProductDto Product
{
get { return (ProductDto)this.GetValue(ProductProperty); }
set { this.SetValue(ProductProperty, value); }
}
<TextBox Margin="2" Text="{Binding Path=Product.Code, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Margin="2" Text="{Binding Path=Product.Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
You should have a Product property for your Window_NewProduct's view model
public class Window_NewProductViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ProductDto product;
public ProductDto Product
{
get
{
return this.product;
}
set
{
if (value != this.product)
{
this.product = value;
NotifyPropertyChanged();
}
}
}
}
Then in the Window_NewProduct xaml you should bind this property to the usercontrols dependency property
<local:ProductUserControl x:Name="ProductUserControl" Product="{Binding Product}"/>
Add parameter to the Window_NewProduct constructor that takes in the ProductDto and passes that to the ViewModel.
public Window_NewProduct(ProductDto product)
{
InitializeComponent();
this.DataContext = new Window_NewProductViewModel() { Product = product };
}
Then in your MainWindow you can just create a new productDto to pass on to the DetailsWindow.
var newProduct = new ProductDto();
var window_NewProduct = new Window_NewProduct(newProduct);
if (window_NewProduct.ShowDialog() == true)
{
Debug.WriteLine(newProduct.Code);
Debug.WriteLine(newProduct.Name);
}
I am caught up in a scenario where I have to dynamically create datagrid columns and must create the columns in C# code. I have a checkbox in a separate area of code for each generated column. The checkbox determines whether or not the specific column is hidden or visible. The checkbox is bound to the GameAttributes.Visible property. However, the DataGrid Visibility property is of a different type. I tried using the BooleanToVisibilityConverter, but still receive a compile error (as I figured).
Does any have any efficient workarounds to this problem?
The error I am encountering:
Cannot implicitly convert type 'bool' to 'System.Windows.Visibility'
EDIT: Compiler error has been resolved, however the binding does not appear to work for visibility.
XAML:
<DataGrid ItemsSource="{Binding}"
DockPanel.Dock="Top"
AutoGenerateColumns="False"
HorizontalAlignment="Stretch"
Name="GameDataGrid"
VerticalAlignment="Stretch"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeRows="False"
IsReadOnly="True"
>
View:
GameAttributes.Add(new GameInfoAttributeViewModel() { Visible = true, Description = "Name", BindingName = "Name" });
GameAttributes.Add(new GameInfoAttributeViewModel() { Visible = false, Description = "Description", BindingName = "Description" });
GameAttributes.Add(new GameInfoAttributeViewModel() { Visible = false, Description = "Game Exists", BindingName = "GameExists" });
foreach (GameInfoAttributeViewModel attribute in GameAttributes)
{
DataGridTextColumn column = new DataGridTextColumn
{
Header = attribute.Description,
Binding = new Binding(attribute.BindingName),
};
Binding visibilityBinding = new Binding();
visibilityBinding.Path = new PropertyPath("Visible");
visibilityBinding.Source = attribute;
visibilityBinding.Converter = new BooleanToVisibilityConverter();
BindingOperations.SetBinding(column, VisibilityProperty, visibilityBinding);
GameDataGrid.Columns.Add(column);
}
ViewModel:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
namespace DonsHyperspinListGenerator
{
class GameInfoAttribute
{
public string Description { get; set; }
public bool Visible { get; set; }
public string BindingName { get; set; }
}
//todo: move to separate class
class GameInfoAttributeViewModel : INotifyPropertyChanged
{
private GameInfoAttribute mGameInfo = new GameInfoAttribute();
public string Description
{
get { return mGameInfo.Description; }
set
{
if (mGameInfo.Description != value)
{
mGameInfo.Description = value;
InvokePropertyChanged("Description");
}
}
}
public bool Visible
{
get { return mGameInfo.Visible; }
set
{
if (mGameInfo.Visible != value)
{
mGameInfo.Visible = value;
InvokePropertyChanged("Visible");
}
}
}
public string BindingName
{
get { return mGameInfo.BindingName; }
set
{
if (mGameInfo.BindingName != value)
{
mGameInfo.BindingName = value;
InvokePropertyChanged("BindingName");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void InvokePropertyChanged(string propertyName)
{
var e = new PropertyChangedEventArgs(propertyName);
PropertyChangedEventHandler changed = PropertyChanged;
if (changed != null) changed(this, e);
}
}
}
Doesn't have anything to do with your binding or your value converter. You're making this assignment:
Visibility = attribute.Visible
where you create a new column in the View.
That's your compile error. Visibility is a System.Windows.Visibility, and attribute.Visible is a bool. You can't set Visibility to a bool. If this value is being set via a Binding anyway, then you really don't need to be setting it manually (indeed it will clear your Binding).
Edit:
Here's an example of how to set the binding in the code behind to use the value converter:
var binding = new Binding();
binding.Source = attribute;
binding.Path = new PropertyPath("Visible");
binding.Converter = (BooleanToVisibilityConverter)Resources["BoolToVisibilityConverter"];
BindingOperations.SetBinding(column, DataGridTemplateColumn.VisibilityProperty, binding);
Second Edit:
I see a couple of things in the above code that may be a problem:
Firstly, when you're setting your binding, it looks like you're setting the binding on just "VisibilityProperty" for your DependencyProperty. I'm not sure what that is in the context of your view (probably UserControl.VisibilityProperty, or something). The specific DependencyProperty that you want to set is on the DataGridTemplateColumn type, so I believe you'll want to set it to DataGridTemplateColumn.VisibilityProperty instead.
So this line: BindingOperations.SetBinding(column, VisibilityProperty, visibilityBinding);
becomes this: BindingOperations.SetBinding(column, DataGridTemplateColumn.VisibilityProperty, visibilityBinding);
Another thing is this line in your object initializer for the DataGridTextColumn:
Binding = new Binding(attribute.BindingName),
I'm not sure what you're doing with that line, but it could possibly be causing an issue with the overall DataContext of the column (which may in turn cause a problem for the Visibility binding). I'm not positive that it's an issue, but it's definitely not needed for setting the Visibility Binding. I had the code that I provided in my answer working in an example project, and all I added where the lines I provided above (after taking out the assignment in the column initializer that had caused the compile error.
If I have a observablecollection in a page that inserts items on a listview. How can I add to that same observablecollection(listview) from a different window(class)? I do not want to use INotifyPropertyChanged and all that. All I'm trying to do is add a item to the existing listview. I have tried literally everything and I can't figure it out. Please any help is appreciated.
CampersPage...(BindingCamper is just my way of basically saying new ObservableCollection()
public partial class CampersPage : Page
{
MainWindow _parentForm;
public GridViewColumnHeader currentColumnSorted = null;
private SortAdorner currentAdorner = null;
String request1;
String request2;
String request3;
String request4;
// public ObservableCollection<Camper> Campers { get; private set; }
public CampersPage(MainWindow parent)
{
_parentForm = parent;
InitializeComponent();
_parentForm.bindings = new BindingCamper();
for (int i = 0; i < _parentForm.allCampers.Count; i++)
{
if (_parentForm.allCampers[i].getRequest(1) != null && _parentForm.allCampers[i].getRequest(2) != null && _parentForm.allCampers[i].getRequest(3) != null && _parentForm.allCampers[i].getRequest(4) != null)
{
request1 = _parentForm.allCampers[i].getRequest(1).getName();
request2 = _parentForm.allCampers[i].getRequest(2).getName();
request3 = _parentForm.allCampers[i].getRequest(3).getName();
request4 = _parentForm.allCampers[i].getRequest(4).getName();
}
_parentForm.bindings.Campers.Add(new Camper { FirstName = "" + _parentForm.allCampers[i].getFirstName(), LastName = "" + _parentForm.allCampers[i].getLastName(), Ages = _parentForm.allCampers[i].getAge(), SchoolGrade = _parentForm.allCampers[i].getGrade(), Gender = "" + _parentForm.allCampers[i].getGender(), bindingRequest1 = request1, bindingRequest2 = request2, bindingRequest3 = request3, bindingRequest4 = request4 });
//DataContext = _parentForm.bindings;
}
DataContext = _parentForm.bindings;
}
---Now I click on a button and a new window comes up where I would like to add a new camper to the listview in CampersPage.
public partial class AddNewCamper : Window
{
MainWindow _parentForm;
public AddNewCamper(MainWindow parentForm)
{
InitializeComponent();
_parentForm = parentForm;
// _parentForm.bindings = new BindingCamper();
}private void btnSubmitNewCamper_Click(object sender, RoutedEventArgs e)
{
String firstName = txtNewFirstName.Text;
String lastName = txtLastName.Text;
int age;
int grade;
String newage = comboNewAge.Text;
if (firstName != "" && lastName != "" && IsNumber(txtNewGrade.Text) && newage != "")
{
age = Convert.ToInt16(newage);
grade = Convert.ToInt16(txtNewGrade.Text);
// Create New Camper
Camper person = new Camper(age, grade, boxNewGender.Text, firstName, lastName);
_parentForm.allCampers.Add(person);
//This is just adding the camper to the listview. Not sure if it is actually adding it to the database.
_parentForm.bindings.Campers.Add(new Camper { FirstName = person.getFirstName(), LastName = person.getLastName(), Ages = person.getAge(), SchoolGrade = person.getGrade() });
//CampersPage p = new CampersPage(_parentForm);
DataContext = _parentForm.bindings;
Do I have to somehow add AddNewCamper's namespace to CampersPage's namespace in xaml?
<ListView HorizontalAlignment="Stretch" Margin="0,12" x:Name ="listViewCampers" ItemsSource="{Binding Campers}" DisplayMemberPath="bindMe" IsSynchronizedWithCurrentItem="True" Grid.Column="1">
ObservableCollection class:
public partial class BindingCamper
{ // This class assist in binding campers from listview to the textboxes on the camperspage
public ObservableCollection<Camper> Campers { get; set; }
public ObservableCollection<Staff> StaffMembers { get; set; }
public ObservableCollection<Schedule> schedule { get; set; }
public ObservableCollection<Group> Groups { get; set; }
public BindingCamper()
{
Campers = new ObservableCollection<Camper>();
StaffMembers = new ObservableCollection<Staff>();
schedule = new ObservableCollection<Schedule>();
Groups = new ObservableCollection<Group>();
}
I won't go as far as claiming you're using WPF wrong, but you're certainly making your life difficult. I suggest reading up a bit on MVVM pattern - it really makes WPF development easier (here's good starting article).
Approach you're using at the moment is not correct on several levels:
your windows/pages need to have way too much knowledge about each other to work properly
result of which, is dependency on parent form in your child windows (while what you really need is dependency on window context, which in fact is campers list)
you need to do too much manual notifications/setting up to achieve your goals (while WPF has great tools to do it automatically)
you seem to be exposing model (allCampers) through view (MainWindow)
All of this can be solved with a bit of redesigning:
Your views (Main, CampersPage, AddNewCamper) should be dependent on BindingCamper class (which essentially could be view model for them), not on each other
Same instance of BindingCamper should be set as DataContext for all of them
You should not add bindings manually (like you're doing now); all can (and should) be done from XAML
Having above in mind, your CampersPage class should look like this:
public partial class CampersPage : Page
{
public CampersPage(BindingCamper camper)
{
InitializeComponent();
DataContext = camper;
}
}
It should by no means initialize data for parent window and set it's binding. This is simply wrong.
Actually, this approach (providing data context through constructor) can be used in all your view classes (AddNewCamper and MainWindow too, probably).
Now, when you need campers page, say from your main window, it gets very easy:
public partial class MainWindow : Window
{
public void ShowCampers()
{
var campersPage = new CampersPage((BindingCampers) this.DataContext);
// show campersPage
}
}
It is the same with AddNewCamper window. Just pass it data context. When you add new camper, add it to BindingCamper.Campers list (which is available through data context):
// rest of the btnSubmitNewCamper_Click method elided
Camper person = new Camper(age, grade, boxNewGender.Text, firstName, lastName);
((BindingCamper)this.DataContext).Campers.Add(person);
That's all. Thanks to combined mechanisms of data binding and observable collection, this new element will immediately be visible both in MainWindow and CampersPage.
Edit:
Code to fetch campers from database should be wrapper with some kind of DAO object (as a part of DAL - I know, lot of ugly buzzwords, but luckily they are all related and fairly obvious). For example, you can have a class that will deal with getting campers from database:
public class CampersProvider
{
public IEnumerable<Camper> GetAllCampers()
{
// here you put all your code for DB interaction
// and simply return campers
}
}
To give you quick solution, you can once again pass CampersProvider to MainWindow constructor, call GetAllCampters method and build observable collection for BindingCamper. However, this is not very MVVM approach. Those stuff usually is handled by view model (yet another, separate class), which at the moment you don't have.
Code you posted requires quite some work, I think it won't be a bad idea if you read a bit about MVVM pattern and try to apply it to your application.