|
1 | | -# How to add multiple trackballs in a WPF SfChart. |
2 | | -This article provides a detailed walkthrough on how to add multiple trackballs in an [SfChart](https://help.syncfusion.com/wpf/charts/getting-started) in WPF, allowing you to hover over the trackball with your mouse and move them independently to view the information of different data points simultaneously. |
3 | | - |
4 | | -Learn step-by-step instructions and gain insights to add multiple trackballs in a WPF SfChart. |
5 | | - |
6 | | -**Step 1:** Create a custom ChartTrackBallBehaviorExt class, which is inherited from [ChartTrackballBehavior](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.ChartTrackBallBehavior.html#). |
7 | | - |
8 | | -C# |
9 | | - |
10 | | - ```csharp |
11 | | -public class ChartTrackBallBehaviorExt : ChartTrackBallBehavior |
12 | | -{ |
13 | | - |
14 | | -} |
15 | | - ``` |
16 | | - |
17 | | -**Step 2:** In the constructor of your MainWindow class, initialize the trackballs by setting their SfChart property to the chart defined in your XAML. This ensures that the trackballs are associated with the correct chart instance and can be accessed in other classes. |
18 | | - |
19 | | -C# |
20 | | - |
21 | | - ```csharp |
22 | | -public partial class MainWindow : Window |
23 | | -{ |
24 | | - public MainWindow() |
25 | | - { |
26 | | - InitializeComponent(); |
27 | | - trackball1.SfChart = this.chart; |
28 | | - trackball2.SfChart = this.chart; |
29 | | - } |
30 | | -} |
31 | | - |
32 | | -public class ChartTrackBallBehaviorExt : ChartTrackBallBehavior |
33 | | -{ |
34 | | - . . . |
35 | | - public SfChart SfChart { get; set; } |
36 | | - . . . |
37 | | -} |
38 | | - ``` |
39 | | - |
40 | | -**Step 3:** Create instances of ChartTrackBallBehaviorExt, and add them to the [Behaviors](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.SfChart.html#Syncfusion_UI_Xaml_Charts_SfChart_Behaviors) collection, assigning specific names to each. |
41 | | - |
42 | | -XAML |
43 | | - |
44 | | - ```XML |
45 | | -<chart:SfChart.Behaviors> |
46 | | - <local:ChartTrackBallBehaviorExt x:Name="trackball1"/> |
47 | | - <local:ChartTrackBallBehaviorExt x:Name="trackball2"/> |
48 | | -</chart:SfChart.Behaviors> |
49 | | - ``` |
50 | | - |
51 | | -**Step 4:** Override the **OnContentRendered** method to run the asynchronous task that calls ShowTrackball(). |
52 | | - |
53 | | -C# |
54 | | - |
55 | | - ```csharp |
56 | | -public partial class MainWindow : Window |
57 | | -{ |
58 | | - . . . |
59 | | - protected override void OnContentRendered(EventArgs e) |
60 | | - { |
61 | | - base.OnContentRendered(e); |
62 | | - |
63 | | - // Run the ShowTrackball method asynchronously |
64 | | - Task.Run(async () => |
65 | | - { |
66 | | - await ShowTrackball(); |
67 | | - }); |
68 | | - } |
69 | | - . . . |
70 | | -} |
71 | | - ``` |
72 | | - |
73 | | -**Step 5:** Implement the **ShowTrackball** method to calculate positions and display the trackballs at load time by using the Display method. |
74 | | - |
75 | | -C# |
76 | | - |
77 | | - ```csharp |
78 | | -public partial class MainWindow : Window |
79 | | -{ |
80 | | - . . . |
81 | | - async Task ShowTrackball() |
82 | | - { |
83 | | - // Wait for 1 second before executing the rest of the method |
84 | | - await Task.Delay(1000); |
85 | | - Application.Current.Dispatcher.Invoke(() => |
86 | | - { |
87 | | - // Calculated positions for the first trackball |
88 | | - float xPosition = (float)chart.ValueToPoint(chart.PrimaryAxis, 1); |
89 | | - float yPosition = (float)chart.ValueToPoint(chart.SecondaryAxis, 169); |
90 | | - |
91 | | - // Calculated positions for the second trackball |
92 | | - float xPosition1 = (float)chart.ValueToPoint(chart.PrimaryAxis, 6); |
93 | | - float yPosition1 = (float)chart.ValueToPoint(chart.SecondaryAxis, 170); |
94 | | - |
95 | | - // Display the first trackball |
96 | | - trackball1.Display(xPosition, yPosition); |
97 | | - |
98 | | - // Display the second trackball |
99 | | - trackball2.Display(xPosition1, yPosition1); |
100 | | - }); |
101 | | - } |
102 | | - . . . |
103 | | - } |
104 | | - ``` |
105 | | - |
106 | | -**Step 6:** Implement the **Display** method, which is responsible for displaying the trackball at specified coordinates . It updates the trackball’s position, sets the IsActivated protected property of the ChartTrackBallBehavior class to true, and triggers the necessary internal methods to reflect these changes on the UI. |
107 | | - |
108 | | -C# |
109 | | - |
110 | | - ```csharp |
111 | | -public class ChartTrackBallBehaviorExt : ChartTrackBallBehavior |
112 | | -{ |
113 | | - . . . |
114 | | - public void Display(float x, float y) |
115 | | - { |
116 | | - X = x; Y = y; |
117 | | - IsActivated = true; |
118 | | - var point = new Point(x, y); |
119 | | - |
120 | | - // Set the internal property for the current point |
121 | | - SetInternalProperty(typeof(ChartTrackBallBehavior), this, point, "CurrentPoint"); |
122 | | - |
123 | | - // Trigger the pointer position changed event |
124 | | - base.OnPointerPositionChanged(); |
125 | | - |
126 | | - // Activate the trackball |
127 | | - InvokeInternalMethod(typeof(ChartTrackBallBehavior), this, "Activate", IsActivated); |
128 | | - } |
129 | | - . . . |
130 | | -} |
131 | | - ``` |
132 | | - |
133 | | -**Step 7:** The **SetInternalProperty** method uses reflection to set an internal property of an object. This is useful for accessing and modifying properties that are not publicly accessible. |
134 | | - |
135 | | -C# |
136 | | - |
137 | | - ```csharp |
138 | | -public class ChartTrackBallBehaviorExt : ChartTrackBallBehavior |
139 | | -{ |
140 | | - . . . |
141 | | - internal static void SetInternalProperty(Type type, object obj, object value, string propertyName) |
142 | | - { |
143 | | - var properties = type.GetRuntimeProperties(); |
144 | | - foreach (var item in properties) |
145 | | - { |
146 | | - if (item.Name == propertyName) |
147 | | - { |
148 | | - item.SetValue(obj, value); |
149 | | - break; |
150 | | - } |
151 | | - } |
152 | | - } |
153 | | - . . . |
154 | | -} |
155 | | - ``` |
156 | | - |
157 | | -**Step 8:** The **InvokeInternalMethod** method uses reflection to invoke an internal method of an object. This allows calling methods that are not publicly accessible. |
158 | | - |
159 | | -C# |
160 | | - |
161 | | - ```csharp |
162 | | -public class ChartTrackBallBehaviorExt : ChartTrackBallBehavior |
163 | | -{ |
164 | | - . . . |
165 | | - internal static object? InvokeInternalMethod(Type type, object obj, string methodName, params object[] args) |
166 | | - { |
167 | | - var method = type.GetTypeInfo().GetDeclaredMethod(methodName); |
168 | | - return method?.Invoke(obj, args); |
169 | | - } |
170 | | - . . . |
171 | | -} |
172 | | - ``` |
173 | | - |
174 | | -**Step 9:** Interact with multiple trackballs by overriding the **Mouse Event handlers** of [ChartTrackBallBehavior](https://help.syncfusion.com/cr/wpf/Syncfusion.UI.Xaml.Charts.ChartTrackBallBehavior.html) class. The **FindNearestTrackball** method is called in **OnMouseEnter** method to find the nearest trackball to the mouse pointer. The **isTrackballActive** variable is used to track whether a specific trackball is currently active, ensuring that only the relevant trackball responds to mouse events. |
175 | | - |
176 | | -C# |
177 | | - |
178 | | - ```csharp |
179 | | -public class ChartTrackBallBehaviorExt : ChartTrackBallBehavior |
180 | | -{ |
181 | | - private bool isTrackballActive = false; |
182 | | - . . . |
183 | | - protected override void OnMouseEnter(MouseEventArgs e) |
184 | | - { |
185 | | - // Get the position of the mouse pointer |
186 | | - var touchPoint = e.GetPosition(null); |
187 | | - |
188 | | - // Find the nearest trackball to the mouse pointer |
189 | | - var trackball = FindNearestTrackball(touchPoint); |
190 | | - |
191 | | - // Activate the trackball if it is the nearest one |
192 | | - if (trackball == this) |
193 | | - { |
194 | | - isTrackballActive = true; |
195 | | - base.OnMouseEnter(e); |
196 | | - } |
197 | | - } |
198 | | - |
199 | | - protected override void OnMouseMove(MouseEventArgs e) |
200 | | - { |
201 | | - // Check if the trackball is activated |
202 | | - if (isTrackballActive) |
203 | | - { |
204 | | - // Get the position of the mouse pointer |
205 | | - var touchPoint = e.GetPosition(null); |
206 | | - |
207 | | - // Display the trackball at the current mouse position |
208 | | - Display((float)touchPoint.X, (float)touchPoint.Y); |
209 | | - base.OnMouseMove(e); |
210 | | - } |
211 | | - } |
212 | | - |
213 | | - protected override void OnMouseLeave(MouseEventArgs e) |
214 | | - { |
215 | | - // Deactivate the trackball |
216 | | - isTrackballActive = false; |
217 | | - } |
218 | | - . . . |
219 | | -} |
220 | | - ``` |
221 | | - |
222 | | -**Step 10:** The **FindNearestTrackball** method identifies the trackball closest to the user’s touch point, determining which trackball should be activated or moved based on user interaction. |
223 | | - |
224 | | -C# |
225 | | - |
226 | | - ```csharp |
227 | | -public class ChartTrackBallBehaviorExt : ChartTrackBallBehavior |
228 | | -{ |
229 | | - . . . |
230 | | - private ChartTrackBallBehavior FindNearestTrackball(Point touchPoint) |
231 | | - { |
232 | | - ChartTrackBallBehavior nearestTrackball = null; |
233 | | - double minDistance = double.MaxValue; |
234 | | - |
235 | | - // Iterate through all trackball behaviors to find the nearest one |
236 | | - foreach (var trackballBehaviour in SfChart.Behaviors) |
237 | | - { |
238 | | - if (trackballBehaviour is ChartTrackBallBehaviorExt trackball) |
239 | | - { |
240 | | - // Calculate the distance between the trackball and the touch point |
241 | | - double distance = Math.Sqrt(Math.Pow(trackball.X - touchPoint.X, 2) + Math.Pow(trackball.Y - touchPoint.Y, 2)); |
242 | | - |
243 | | - // Update the nearest trackball if the current one is closer |
244 | | - if (distance < minDistance) |
245 | | - { |
246 | | - minDistance = distance; |
247 | | - nearestTrackball = trackball; |
248 | | - } |
249 | | - } |
250 | | - } |
251 | | - |
252 | | - return nearestTrackball; |
253 | | - } |
254 | | - . . . |
255 | | - } |
256 | | - ``` |
257 | | - |
258 | | -**Step 11:** To control the trackballs, simply hover over them with your mouse. As you move the mouse within the chart area, the trackball will follow the cursor, allowing you to inspect different data points interactively. |
259 | | - |
260 | | -**Output:** |
261 | | - |
262 | | - |
263 | | - |
264 | | - |
265 | | -## Troubleshooting |
266 | | -### Path too long exception |
267 | | -If you are facing a path too long exception when building this example project, close Visual Studio and rename the repository to short and build the project. |
268 | | - |
269 | | -For more details, refer to the KB on [How to add multiple trackballs in a WPF SfChart?](https://support.syncfusion.com/agent/kb/17741/edit). |
270 | | - |
271 | | - |
272 | | - |
| 1 | +# How to add multiple trackballs in a WPF SfChart. |
0 commit comments