This is a migrated thread and some comments may be shown as answers.

[Solved] Blend Trigger for Double Click for RadGridViewRow

7 Answers 394 Views
GridView
This is a migrated thread and some comments may be shown as answers.
This question is locked. New answers and comments are not allowed.
Joseph Gershgorin
Top achievements
Rank 1
Joseph Gershgorin asked on 16 Jun 2010, 05:24 AM
Just thought I'd share this snippet for others that may find this code useful. I had a read only grid that I wanted to execute a viewmodel command in response to a double click anywhere on a row (even on a row portion where a cell might not exist).

Here it is implemented via a Blend Behavior Trigger (but could be easily re factored for other uses):
using System; 
using System.Windows; 
using System.Windows.Input; 
using System.Windows.Interactivity; 
using System.Linq; 
using System.Collections.Generic; 
using System.Windows.Media; 
using Telerik.Windows.Controls; 
using Telerik.Windows.Controls.GridView; 
 
namespace MyApp.Behaviors.Triggers 
{     
    public class RadGridViewRowMouseDoubleClickTrigger : TriggerBase<RadGridView> 
    {        
        private MouseButtonEventHandler _gridViewMouseLeftButtonDownHandler; 
        private DateTime _lastMouseDownDateTime = DateTime.Now;              
        private bool _hasFirstMouseDownOccured = false
        private Point _firstMouseDownPosition; 
        
        protected override void OnAttached() 
        { 
           if (AssociatedObject != null
           { 
               _gridViewMouseLeftButtonDownHandler = new MouseButtonEventHandler(OnGridViewMouseLeftButtonDown); 
               AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent, _gridViewMouseLeftButtonDownHandler, true);                
           } 
        } 
 
        protected override void OnDetaching() 
        { 
            if (AssociatedObject != null && _gridViewMouseLeftButtonDownHandler != null
            { 
                AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent, _gridViewMouseLeftButtonDownHandler); 
            } 
        } 
 
        private static bool IsGridViewRow(RadGridView radGridView, Point mousePosition, ref object rowDataContext) 
        {             
            GeneralTransform gt = radGridView.TransformToVisual(Application.Current.RootVisual); 
            Point mousePositionOffset = gt.Transform(mousePosition); 
          
            IEnumerable<UIElement> hitElements = VisualTreeHelper.FindElementsInHostCoordinates(mousePositionOffset, radGridView); 
 
            var gridViewRow = hitElements.OfType<GridViewRow>().FirstOrDefault(); 
 
            if (gridViewRow != null
            { 
                rowDataContext = gridViewRow.DataContext; 
            } 
 
            return gridViewRow != null;               
        } 
 
        private void OnGridViewMouseLeftButtonDown(object sender, MouseButtonEventArgs args) 
        { 
            DateTime currentMouseDownDateTime = DateTime.Now; 
            TimeSpan timeSpanBetweenButtonDowns = currentMouseDownDateTime - _lastMouseDownDateTime; 
 
            //First Mouse Down 
            if (timeSpanBetweenButtonDowns.TotalMilliseconds > 300 || _hasFirstMouseDownOccured == false)  
            {          
                _firstMouseDownPosition = args.GetPosition(AssociatedObject);  
                _hasFirstMouseDownOccured = true;  
                _lastMouseDownDateTime = DateTime.Now;  
            }  
            else  
            { 
                Point currentMousePosition = args.GetPosition(AssociatedObject); 
 
                object rowDataContext = null
 
                // Second Mouse Down within 300 miliseconds 
                // Mouse hasn't moved since previous left button down,  
                // and mouse pointer is within a GridViewRow 
                if ( (Math.Abs(_firstMouseDownPosition.X - currentMousePosition.X) < 4 && Math.Abs(_firstMouseDownPosition.Y - currentMousePosition.Y) < 4) 
                    && IsGridViewRow(AssociatedObject, currentMousePosition, ref rowDataContext) 
                    ) 
                { 
                    InvokeActions(rowDataContext);                                         
                }             
                          
                _hasFirstMouseDownOccured = false;  
           }                        
        }        
    } 
 

XAML:
<telerik:RadGridView 
SelectionMode="Single"                             
IsReadOnly="True" 
ItemsSource="{Binding Path=TestData}"                              
> 
    <i:Interaction.Triggers> 
        <Triggers:RadGridViewRowMouseDoubleClickTrigger> 
            <Triggers:RadGridViewRowMouseDoubleClickTrigger.Actions> 
                <Behaviors1:ExecuteCommandAction 
                    Command="{Binding Path=TestCommand}" /> 
            </Triggers:RadGridViewRowMouseDoubleClickTrigger.Actions> 
        </Triggers:RadGridViewRowMouseDoubleClickTrigger> 
    </i:Interaction.Triggers> 
</telerik:RadGridView> 

7 Answers, 1 is accepted

Sort by
0
Accepted
Vlad
Telerik team
answered on 16 Jun 2010, 06:18 AM
Hello,

 Thank you very much! I've added 1000 Telerik points to your account.

Regards,
Vlad
the Telerik team

Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items.
0
Laverson
Top achievements
Rank 1
answered on 14 Jul 2010, 01:12 PM
Hello,

Very nice !... Congratulations... It's will be very useful.

Can you give me an example how to implement programatically via code ?

Best Regards,
Laverson



0
Vlad
Telerik team
answered on 15 Jul 2010, 01:15 PM
Hi Laverson,

 Why not use RowActivated event instead? You can check this demo for more info.

Sincerely yours,
Vlad
the Telerik team
Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Public Issue Tracking system and vote to affect the priority of the items
0
Laverson
Top achievements
Rank 1
answered on 16 Jul 2010, 07:05 PM
Hi Vlad 

As your suggestion I implemented the RowActivated it's solved my problem .

Thank you so much.
 
Regards,
Laverson
0
SHEIKH
Top achievements
Rank 1
answered on 10 Jan 2011, 10:29 AM
Hi there.
Joseph, your code really works thanks for the solution. I've bin stuck with this for almost a week now. Finally saw your code, tried it and it really works. Thank you so very much. I've never used any "TriggerBase" and "ExecuteCommandAction" so far. I'd digg it further to get some better picture. If anyone can explain the abstract just for the reference then it will be great.

Just quoting my problem that I've been facing with RowActivated scenario here for double clicking a row.
Here is my GridView XAML set up for the "RowActivated" .

<Controls1:RadGridView Grid.Column="0"  Grid.Row="0"  Width="Auto" BorderThickness="0" HorizontalAlignment="Left"
                                        CanUserFreezeColumns="False" ScrollMode="RealTime" IsReadOnly="False" x:Name="RadGridView1"
                                        GridLinesVisibility="None" AutoGenerateColumns="False"
                                        ShowGroupPanel="False"          
                                        ShowColumnHeaders="False"
                                         IsFilteringAllowed="False" RowIndicatorVisibility="Collapsed"                             
                                         RowStyle="{StaticResource rowStyle}"
                                         ItemsSource="{Binding PagedSource, ElementName=RadGridPager, Mode=TwoWay}"
                                         Background="Transparent"  VerticalAlignment="Top"
                                         SelectedItem="{Binding SelectedItem,Mode=TwoWay}"                                                  
                                         RowActivated="RadGridView1_RowActivated"
                                         IsSynchronizedWithCurrentItem="False"         
                                         >

here I'm using the rowStyle, which is like:
<Style x:Key="rowStyle" TargetType="GridView:GridViewRow" >
                                <Setter Property="Template" Value="{StaticResource MyCustomRowTemplate}" />
                                <Setter Property="Height" Value="55" />
                                <Setter Property="Width" Value="650" />
                                <Setter Property="Background" Value="Transparent" />
                            </Style>

and my custom row template looks like.

<ControlTemplate x:Key="MyCustomRowTemplate" TargetType="GridView:GridViewRow">
                                <Border  x:Name="selectedRow" Margin="2,2,2,2" CornerRadius="5"
                            BorderThickness="1" BorderBrush="#FF000000" RenderTransformOrigin="0.5,0.5">
                                    <Border.RenderTransform>
                                        <CompositeTransform/>
                                    </Border.RenderTransform>
                                    <Border.Background>
                                        <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                            <GradientStop Color="#FF265574" Offset="0"/>
                                            <GradientStop Color="#FF1B4058" Offset="1"/>
                                        </LinearGradientBrush>
                                    </Border.Background>
                                    <VisualStateManager.VisualStateGroups>
                                        <VisualStateGroup x:Name="EditStates">
                                            <VisualState x:Name="ReadOnlyMode"/>
                                            <VisualState x:Name="EditMode"/>
                                        </VisualStateGroup>
                                        <VisualStateGroup x:Name="CommonStates">
                                            <VisualStateGroup.Transitions>
                                                <VisualTransition GeneratedDuration="0:0:0.2" To="MouseOver">
                                                    <Storyboard/>
                                                </VisualTransition>
                                                <VisualTransition From="MouseOver" GeneratedDuration="0:0:0.2" To="Normal"/>
                                            </VisualStateGroup.Transitions>
                                            <VisualState x:Name="Normal"/>
                                            <VisualState x:Name="MouseOver">
                                                <Storyboard>
                                                    <ColorAnimation Duration="0" To="#FFFEFFFD" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="selectedRow" d:IsOptimized="True"/>
                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderThickness)" Storyboard.TargetName="selectedRow">
                                                        <DiscreteObjectKeyFrame KeyTime="0">
                                                            <DiscreteObjectKeyFrame.Value>
                                                                <Thickness>1</Thickness>
                                                            </DiscreteObjectKeyFrame.Value>
                                                        </DiscreteObjectKeyFrame>
                                                    </ObjectAnimationUsingKeyFrames>
                                                    <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="selectedRow" d:IsOptimized="True"/>
                                                    <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="selectedRow" d:IsOptimized="True"/>
                                                    <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="selectedRow" d:IsOptimized="True"/>
                                                </Storyboard>
                                            </VisualState>
                                        </VisualStateGroup>
                                        <VisualStateGroup x:Name="SelectionStates">
                                            <VisualStateGroup.Transitions>
                                                <VisualTransition GeneratedDuration="0:0:0.2" To="Selected">
                                                    <Storyboard/>
                                                </VisualTransition>
                                            </VisualStateGroup.Transitions>
                                            <VisualState x:Name="Selected">
                                                <Storyboard>
                                                    <ColorAnimation Duration="0" To="#FFFEFFFD" Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)" Storyboard.TargetName="selectedRow" d:IsOptimized="True"/>
                                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderThickness)" Storyboard.TargetName="selectedRow">
                                                        <DiscreteObjectKeyFrame KeyTime="0">
                                                            <DiscreteObjectKeyFrame.Value>
                                                                <Thickness>2</Thickness>
                                                            </DiscreteObjectKeyFrame.Value>
                                                        </DiscreteObjectKeyFrame>
                                                    </ObjectAnimationUsingKeyFrames>
                                                    <DoubleAnimation Duration="0" To="0.2" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="selectedRow" d:IsOptimized="True"/>
                                                    <DoubleAnimation Duration="0" To="0.2" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateY)" Storyboard.TargetName="selectedRow" d:IsOptimized="True"/>
                                                    <DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="selectedRow" d:IsOptimized="True"/>
                                                </Storyboard>
                                            </VisualState>
                                            <VisualState x:Name="Unselected"/>
                                        </VisualStateGroup>
                                        <VisualStateGroup x:Name="FocusStates">
                                            <VisualState x:Name="Focused"/>
                                        </VisualStateGroup>
                                    </VisualStateManager.VisualStateGroups>
                                    <StackPanel Orientation="Horizontal" Background="Transparent"  >
                                        <StackPanel>
                                            <Image Source="/IST.Modules.Navigation;Component/Resources/MainGridRecord.png" HorizontalAlignment="Left" Width="32" Height="32" Margin="1,8,1,0"/>
                                        </StackPanel>
                                        <StackPanel Orientation="Vertical">
                                            <TextBlock Text="{Binding CustomerName}" Foreground="White" FontFamily="Calibri" FontSize="13.333" FontWeight="Bold" Margin="5,7,0,0" />
                                            <StackPanel Orientation="Horizontal" Margin="5,1,0,0">
                                                <TextBlock Text="{Binding CustomerID}" Foreground="#FF7D8D9A" FontFamily="Calibri" FontSize="13.333" />
                                                <TextBlock Text="|" Foreground="#FF7D8D9A" FontFamily="Calibri" FontSize="13.333" />
                                                <TextBlock Text="{Binding AccountNumber}" Foreground="#FF7D8D9A" />
                                                <TextBlock Text="|" Foreground="#FF7D8D9A" FontFamily="Calibri" FontSize="13.333" />
                                                <TextBlock Text="{Binding URL}" Foreground="#FF7D8D9A" />
                                                <TextBlock Text="|" Foreground="#FF7D8D9A" FontFamily="Calibri" FontSize="13.333" />
                                                <TextBlock Text="{Binding AccountBalance}" Foreground="#FF7D8D9A" />                                               
                                            </StackPanel>
                                        </StackPanel>
                                    </StackPanel>
                                </Border>
                            </ControlTemplate>


The problem is that my "RowActivated" doesn't get fired at all. First, I was thinking may be there is some problem with my commandbehavior but then I used event handler in the code behind and still the problem remains the same i.e. no RowActivated event is fired. I think telerik guys can better speak on that part.

anyways keep sharing.

regards,
0
Maya
Telerik team
answered on 11 Jan 2011, 02:56 PM
Hello,

Generally, the RowActivated event is raised on the presence of GridViewCell-s. As you predefine the GridViewRow template and remove the GridViewCells, the event cannot be fired. You may take a look at the other forum thread on a similar topic for a reference and a sample project.
 

Best wishes,
Maya
the Telerik team
Let us know about your Windows Phone 7 application built with RadControls and we will help you promote it. Learn more>>
0
Joseph Gershgorin
Top achievements
Rank 1
answered on 25 Jan 2011, 10:29 PM
Here's an enhanced version of my behavior, which adds two new properties to the trigger:

IgnoreDetailsPresenter - If true double click is ignored if the double click occurs in the row details area of the row (if it has a row details presenter).

IgnoreDetailsPresenterUnlessSecondClick - If true will process second click on details are as a double click only if the first click was on a gridviewrow (not details) and the second click is in the same pixel margin area and same row as the first click. I added this because on first click my mouse would expand the row details  and even though my mouse hasn't moved it's technically under the details view for the second click. 

Here's the code:
using System;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Interactivity;
    using System.Linq;
    using System.Collections.Generic;
    using System.Windows.Media;
 
    using global::Telerik.Windows.Controls;
    using global::Telerik.Windows.Controls.GridView;
 
    //Last Updated 1/25/2011 12:00 AM
    public class RadGridViewRowMouseDoubleClickTrigger : TriggerBase<RadGridView>
    {
        private static readonly Type TriggerType = typeof(RadGridViewRowMouseDoubleClickTrigger);
        private MouseButtonEventHandler _gridViewMouseLeftButtonDownHandler;
        private DateTime _lastMouseDownDateTime = DateTime.Now;            
        private bool _hasFirstMouseDownOccured = false;
        private Point _firstMouseDownPosition;
        
        #region Dependency Properties
 
        #region DelayBetweenClicks Dependency Property
        public static readonly DependencyProperty DelayBetweenClicksProperty =
           DependencyProperty.Register("DelayBetweenClicks", typeof(TimeSpan), TriggerType,
           new PropertyMetadata(TimeSpan.FromMilliseconds(500), OnDelayBetweenClicksPropertyChanged));
 
        private static void OnDelayBetweenClicksPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((RadGridViewRowMouseDoubleClickTrigger)sender).OnDelayBetweenClicksPropertyChanged(e);
        }
 
        protected virtual void OnDelayBetweenClicksPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
 
        }
 
        public TimeSpan DelayBetweenClicks
        {
            get { return (TimeSpan)GetValue(DelayBetweenClicksProperty); }
            set { SetValue(DelayBetweenClicksProperty, value); }
        }
        #endregion DelayBetweenClicks Dependency Property
 
        #region AllowedPixelMargin Dependency Property
        public static readonly DependencyProperty AllowedPixelMarginProperty =
           DependencyProperty.Register("AllowedPixelMargin", typeof(int), TriggerType,
           new PropertyMetadata(4, OnAllowedPixelMarginPropertyChanged));
 
        private static void OnAllowedPixelMarginPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((RadGridViewRowMouseDoubleClickTrigger)sender).OnAllowedPixelMarginPropertyChanged(e);
        }
 
        protected virtual void OnAllowedPixelMarginPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
 
        }
 
        public int AllowedPixelMargin
        {
            get { return (int)GetValue(AllowedPixelMarginProperty); }
            set { SetValue(AllowedPixelMarginProperty, value); }
        }
        #endregion AllowedPixelMargin Dependency Property
 
        #region IgnoreDetailsPresenter Dependency Property
        public static readonly DependencyProperty IgnoreDetailsPresenterProperty =
           DependencyProperty.Register("IgnoreDetailsPresenter", typeof(bool), TriggerType,
           new PropertyMetadata(false, OnIgnoreDetailsPresenterPropertyPropertyChanged));
 
        private static void OnIgnoreDetailsPresenterPropertyPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((RadGridViewRowMouseDoubleClickTrigger)sender).OnIgnoreDetailsPresenterPropertyPropertyChanged(e);
        }
 
        protected virtual void OnIgnoreDetailsPresenterPropertyPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
 
        }
 
        public bool IgnoreDetailsPresenter
        {
            get { return (bool)GetValue(IgnoreDetailsPresenterProperty); }
            set { SetValue(IgnoreDetailsPresenterProperty, value); }
        }
        #endregion IgnoreDetailsPresenter Dependency Property
 
        #region IgnoreDetailsPresenterUnlessSecondClick Dependency Property
        public static readonly DependencyProperty IgnoreDetailsPresenterUnlessSecondClickProperty =
           DependencyProperty.Register("IgnoreDetailsPresenterUnlessSecondClick", typeof(bool), TriggerType,
           new PropertyMetadata(false, OnIgnoreDetailsPresenterUnlessSecondClickPropertyPropertyChanged));
 
        private static void OnIgnoreDetailsPresenterUnlessSecondClickPropertyPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            ((RadGridViewRowMouseDoubleClickTrigger)sender).OnIgnoreDetailsPresenterUnlessSecondClickPropertyPropertyChanged(e);
        }
 
        protected virtual void OnIgnoreDetailsPresenterUnlessSecondClickPropertyPropertyChanged(DependencyPropertyChangedEventArgs e)
        {
 
        }
 
        public bool IgnoreDetailsPresenterUnlessSecondClick
        {
            get { return (bool)GetValue(IgnoreDetailsPresenterUnlessSecondClickProperty); }
            set { SetValue(IgnoreDetailsPresenterUnlessSecondClickProperty, value); }
        }
        #endregion IgnoreDetailsPresenterUnlessSecondClick Dependency Property
 
        #endregion Dependency Properties
 
        protected override void OnAttached()
        {
           if (AssociatedObject != null)
           {
               _gridViewMouseLeftButtonDownHandler = new MouseButtonEventHandler(OnGridViewMouseLeftButtonDown);
               AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent, _gridViewMouseLeftButtonDownHandler, true);              
           }
        }
 
        protected override void OnDetaching()
        {
            if (AssociatedObject != null && _gridViewMouseLeftButtonDownHandler != null)
            {
                AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent, _gridViewMouseLeftButtonDownHandler);
                _gridViewMouseLeftButtonDownHandler = null;
            }
        }
 
        private GridViewRow _firstMouseLeftButtonDownGridViewRow;
 
        private bool IsValidGridViewRowLocation(RadGridView radGridView, Point mousePosition, ref object rowDataContext, bool isSecondClick)
        {           
            GeneralTransform gt = radGridView.TransformToVisual(Application.Current.RootVisual);
            Point mousePositionOffset = gt.Transform(mousePosition);
          
            IEnumerable<UIElement> hitElements = VisualTreeHelper.FindElementsInHostCoordinates(mousePositionOffset, radGridView);
 
            if (IgnoreDetailsPresenter || (isSecondClick == false && IgnoreDetailsPresenterUnlessSecondClick) )
            {
                var gridDetails = hitElements.OfType<DetailsPresenter>().FirstOrDefault();
 
                if (gridDetails != null)
                {
                    return false;
                }   
            }
             
            var gridViewRow = hitElements.OfType<GridViewRow>().FirstOrDefault();
 
            if (gridViewRow != null)
            {
                rowDataContext = gridViewRow.DataContext;
            }
 
            if (isSecondClick)
            {
                return gridViewRow != null && gridViewRow == _firstMouseLeftButtonDownGridViewRow;               
            }
             
            _firstMouseLeftButtonDownGridViewRow = gridViewRow;
            return gridViewRow != null;                                   
        }
 
        private void OnGridViewMouseLeftButtonDown(object sender, MouseButtonEventArgs args)
        {
            DateTime currentMouseDownDateTime = DateTime.Now;
            TimeSpan timeSpanBetweenButtonDowns = currentMouseDownDateTime - _lastMouseDownDateTime;
 
            Point currentMousePosition = args.GetPosition(AssociatedObject);
               object rowDataContext = null;
 
            // First Mouse Down
            if (timeSpanBetweenButtonDowns > DelayBetweenClicks || _hasFirstMouseDownOccured == false)
            {                        
                if (IsValidGridViewRowLocation(AssociatedObject, currentMousePosition, ref rowDataContext, false))
                {
                    _firstMouseDownPosition = currentMousePosition;
                    _hasFirstMouseDownOccured = true;
                    _lastMouseDownDateTime = DateTime.Now;
                }
            }
            else
            {                            
                // Second mouse down is
                // A.) Within specified miliseconds of first mouse down.
                // B.) Allowed pixels margin of first mouse down
                // C.) Pointer position is within a GridViewRow
                if ((Math.Abs(_firstMouseDownPosition.X - currentMousePosition.X) < AllowedPixelMargin && Math.Abs(_firstMouseDownPosition.Y - currentMousePosition.Y) < AllowedPixelMargin)
                    && IsValidGridViewRowLocation(AssociatedObject, currentMousePosition, ref rowDataContext, true)
                    )
                {
                    InvokeActions(args);                                       
                }           
                          
                _hasFirstMouseDownOccured = false;
           }                      
        }      
    }
Tags
GridView
Asked by
Joseph Gershgorin
Top achievements
Rank 1
Answers by
Vlad
Telerik team
Laverson
Top achievements
Rank 1
SHEIKH
Top achievements
Rank 1
Maya
Telerik team
Joseph Gershgorin
Top achievements
Rank 1
Share this question
or