ListView binding / Command

voidstreamvoidstream FRMember ✭✭✭

Hello,

I have a binding on a ListView and i want make an action when i tap on my image. How can i do that?

My XAML:

            <ListView x:Name="LV_Attachments"
                      AbsoluteLayout.LayoutBounds="0,0,1,1"
                      AbsoluteLayout.LayoutFlags="All"
                      RowHeight="50">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid ColumnSpacing="0">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="30"/>
                                    <ColumnDefinition Width="*"/>
                                    <ColumnDefinition Width="20"/>
                                </Grid.ColumnDefinitions>
                                <Image Grid.Column="0"/>
                                <Label Grid.Column="1" Text="{Binding OriginalFilename}"/>
                                <Image Grid.Column="2">
                                    <Image.GestureRecognizers>
                                        <TapGestureRecognizer Command="DeleteAttachment" CommandParameter="{Binding ID}"/>
                                    </Image.GestureRecognizers>
                                </Image>
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

My code behind:

        public Command DeleteAttachment
        {
            get
            {
                return new Command<int>( /* ???? */);
            }
        }

Best Answer

Answers

  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭

    You can't return a new command every tiem you try to get it. You need one instance.
    Here is an example taken from a working project for being able to accept a scan

            #region AcceptScan 
            public ICommand acceptScanCommand;
    
            public ICommand AcceptScanCommand => acceptScanCommand;
    
            private bool CanAcceptScanCommand(object arg)
            {
                //The rules for being able to accept a scan:
                //  if (someRuleIsBroken) return false;
                return true;
            }
    
            #endregion AcceptScan
    

    then elsewhere such as the constructor on OnStart() assign a handler for when it is executed and a method for evaluating if its legal to execute the command.

                acceptScanCommand = new Command(On_AcceptScanCommand, CanAcceptScanCommand);
    

    The handler for when it gets exectuted

            #region Command Handlers
            #region On_AcceptScanCommand
    
            private async void On_AcceptScanCommand(object filePathObj)
            {
                // Your code
            }
    

    Then in your XAML you just assign the command to a button or whatever. It will enable/disable automatically when the CanAcceptScanCommand is (re)evaluated.

                    <Button HeightRequest="55"
                            Margin="0,7,0,0"
                            VerticalOptions="CenterAndExpand"
                            BackgroundColor="Green"
                            Command="{Binding AcceptScanCommand}"
    
  • voidstreamvoidstream FRMember ✭✭✭

    @ClintStLaurent Thanks! I'm disturbed because i never use Interface when i was coding on WinForms. I saw you use ICommand and not Command, why?
    Why you define 2 ICommand? You can't just define 1 ICommand and do the action?

                    public ICommand acceptScanCommand;
    
                    public ICommand AcceptScanCommand => acceptScanCommand;
    
  • ClintStLaurentClintStLaurent USUniversity ✭✭✭✭✭
    edited December 2016

    @TonyPinot
    The first (lower case 'a') is a field. It is the backing for the property.
    The second (upper case 'A' is a property. You can't bind to fields, you can only bind to properties.

    By specifying an interface "ICommand" then we can assign it any kind of command, including commands inherited from Command class.

    If you're coming over from WinForms without having done any WPF then you've got about 20 years of catching up to do. I talk about that a lot on DreamInCode.net - So many developers that either never left the 1990's WinForms because their jobs never needed them to, or being fresh out of college where they haven't updated their curriculums in 15 years. Now they are jumping in to Xamarin because they are recognizing they fell behind the development curve and want to get into mobile development, but have no experience with the C#/XAML/MVVM design pattern. So now binding, commands, all of that is totally new to them. I'm working on a tutorial series to help with that transition, that I'm hoping to have posted by the start of the year.

  • voidstreamvoidstream FRMember ✭✭✭
    edited December 2016

    Well so you can replace your code by:

    public ICommand AcceptScanCommand { get; private set; } = new Command(On_AcceptScanCommand, CanAcceptScanCommand);

    I'm newbie on C# Winforms (less 2years) and i never use WPF, XAML, MVVM design pattern. I know i must split design/code and i was working like this:
    WinForms + Code behind = Design management
    Code behind => binding to data class
    But i never use INotify and others IClass, i just use events "Clicked,Selected etc..."

    After reading i understand the MVVM concept but i'm just disturbed by one point, why the ViewModel can be the code behind XAML?

  • voidstreamvoidstream FRMember ✭✭✭
    edited December 2016

    I don't have a time to switch my view-model design patern to mvvvm.

    My XAML:

        <Image.GestureRecognizers>
              <TapGestureRecognizer Command="DeleteAttachment" CommandParameter="{Binding ID}"/>
        </Image.GestureRecognizers>
    

    My code behind XAML:

    public Command DeleteAttachment { get; private set; } = new Command<int>(parameter => Debug.WriteLine("Command executed: {0}", parameter));

    I got this error:
    "Cannot assign property "Command": Property does not exists, or is not assignable, or mismatching type between value and property"

  • NamyslawSzymaniukNamyslawSzymaniuk USMember ✭✭✭✭

    @NMackay said:
    @TonyPinot

    Unless the command exists in your data template binding object that code won't work, the binding system is looking in that object for that command.

    I have no idea how your page's binding context is set but you could try relative source binding.

    Give you page a name attribute

    x:Name="MyPage"

    In your data template

        <Image.GestureRecognizers>
              <TapGestureRecognizer Command="{Binding Source={x:Reference MyPage}, Path=BindingContext.DeleteAttachment" CommandParameter="{Binding ID}"/>
        </Image.GestureRecognizers>
    

    I agree, it works properly for me, from a long time, on Android as well as on iOS.

    Just maybe one hint - you can pass whole object (Attachment I guess), instead on ID only, by changing:
    <TapGestureRecognizer Command="{Binding Source={x:Reference MyPage}, Path=BindingContext.DeleteAttachment" CommandParameter="{Binding ID}"/>
    to:
    <TapGestureRecognizer Command="{Binding Source={x:Reference MyPage}, Path=BindingContext.DeleteAttachment" CommandParameter="{Binding .}"/>

  • voidstreamvoidstream FRMember ✭✭✭
    edited December 2016

    @NamyslawSzymaniuk Thanks for your answer, i will use later the dot but for the test phase i prefer use ID ^^
    @NMackay
    Thanks for your answer:
    I add BindingContext = this; and your XAML and it's work :D

  • NMackayNMackay GBInsider, University mod

    @TonyPinot

    Good stuff :smiley:

Sign In or Register to comment.