Situation:
I have one Chart and three ChartArea that are aligned in view, zoom, cursor through the AxisViewChanged method that act in this way:
if (e.Axis == ax1)
{
ax2.ScaleView.Size = ax1.ScaleView.Size;
ax2.ScaleView.Position = ax1.ScaleView.Position;
ax3.ScaleView.Size = ax1.ScaleView.Size;
ax3.ScaleView.Position = ax1.ScaleView.Position;
min = (int)ax1.ScaleView.ViewMinimum;
max = (int)ax1.ScaleView.ViewMaximum;
}
if (e.Axis == ax2)
{
....
And it works very well in both cases: when I zoom in/out or scroll.
Problem:
The problem is that my graph source is made by a lot of points, in the worst case we talk about 3'600'000 samples. With this amount of samples, when I move around points with cursor and try to show a tooltip with the values, the interaction quality collapse and becomes unusables (even having set Fast Line).
So I tried to implement a simple decimation algorithm to reducethe number of showed points:
void draw_graph(int start, int end)
{
double fract_value = 0;
int int_value = 0;
int num_saples_selected = end - start;
if(num_saples_selected <= MAX_GRAPH_NUM_SAMPLES)
fill_graphs_point(0, end, 1);
else
{
fract_value = ((double)num_saples_selected) / ((double)MAX_GRAPH_NUM_SAMPLES);
int_value = (int)fract_value;
if (fract_value > int_value)
int_value++;
fill_graphs_point(0, end, int_value);
}
}
void fill_graphs_point(int start, int end, int step)
{
int i = 0;
for (i = start; i < end; i=i+step)
{
dlChart.Series[SERIES_VOLTAGE].Points.AddXY(timestamps_array[i], voltage_array[i]);
dlChart.Series[SERIES_CURRENT].Points.AddXY(timestamps_array[i], current_array[i]);
dlChart.Series[SERIES_ENERGY].Points.AddXY(timestamps_array[i], energy_array[i]);
// I will use this to came back to real position of the initial array
decimation_positions.Add(i);
}
}
Assuminig I had a good idea with this method to reduce the points number, I do not know where to put the call to the function "draw_graph". If I put it in the AxisViewChanged method it will call my method also when I scroll (horizontally) my graph and this is not what I want. I want to call my method only on zoom and unzoom event.
Expected behavior: in the first view (without any zoom) the graph has to show an "idea" of the trend of the graph. Then for every selection/(un)zoom I want to call my function to check if the points number of selected portion will fit in my window size that is MAX_GRAPH_NUM_SAMPLES(=10000).
Hope someone can help me. Whatever kind of suggestion will be appreciated. Thanks in advance.
I forgot to say that strangely the problem appeared when I zoomed in more than one time. At some point also the PC fan started. I resolved disabling the library zoom and implement my self a sort of zoom (a little bit more simple). The solution is in this method and the method I write in the question:
private void dlChart_SelectionRangeChange(object sender, CursorEventArgs e)
{
double startSelection = e.NewSelectionStart;
double endSelection = e.NewSelectionEnd;
// Allow the selection to disappear
reset_selection();
try
{
// before convert point from "showed point" to "real point" I check the limits
if (
(startSelection >= 0) && (startSelection <= decimation_positions.Count()) &&
(endSelection >= 0) && (endSelection <= decimation_positions.Count()) &&
(endSelection != startSelection)
)
{
// convert
startSelection = decimation_positions[(int)startSelection];
endSelection = decimation_positions[(int)endSelection];
// check for reverse zoom (from right to left)
if (startSelection > endSelection)
{
double tmp = startSelection;
startSelection = endSelection;
endSelection = tmp;
}
// clean the series
this.reset_series();
// draw the selected range
draw_graph((int)startSelection, (int)endSelection);
}
}catch(ArgumentOutOfRangeException ex)
{
// todo
}
//System.Console.WriteLine("SelSTART = "+e.NewSelectionStart+" SelEND = "+e.NewSelectionEnd);
}
Related
In the app I'm trying to develop a key part is getting the position of where the user has touched. First I thought of using a tap gesture recognizer but after a quick google search I learned that was useless (See here for an example).
Then I believe I discovered SkiaSharp and after learning how to use it, at least somewhat, I'm still not sure how I get the proper coordinates of a touch. Here are sections of the code in my project that are relevant to the problem.
Canvas Touch Function
private void canvasView_Touch(object sender, SKTouchEventArgs e)
{
// Only carry on with this function if the image is already on screen.
if(m_isImageDisplayed)
{
// Use switch to get what type of action occurred.
switch (e.ActionType)
{
case SKTouchAction.Pressed:
TouchImage(e.Location);
// Update simply tries to draw a small square using double for loops.
m_editedBm = Update(sender);
// Refresh screen.
(sender as SKCanvasView).InvalidateSurface();
break;
default:
break;
}
}
}
Touch Image
private void TouchImage(SKPoint point)
{
// Is the point in range of the canvas?
if(point.X >= m_x && point.X <= (m_editedCanvasSize.Width + m_x) &&
point.Y >= m_y && point.Y <= (m_editedCanvasSize.Height + m_y))
{
// Save the point for later and set the boolean to true so the algorithm can begin.
m_clickPoint = point;
m_updateAlgorithm = true;
}
}
Here I'm just seeing or TRYING to see if the point clicked was in range of the image and I made a different SkSize variable to help. Ignore the boolean, not that important.
Update function (function that attempts to draw ON the point pressed so it's the most important)
public SKBitmap Update(object sender)
{
// Create the default test color to replace current pixel colors in the bitmap.
SKColor color = new SKColor(255, 255, 255);
// Create a new surface with the current bitmap.
using (var surface = new SKCanvas(m_editedBm))
{
/* According to this: https://learn.microsoft.com/en-us/xamarin/xamarin-forms/user-interface/graphics/skiasharp/paths/finger-paint ,
the points I have to start are in Xamarin forms coordinates, but I need to translate them to SkiaSharp coordinates which are in
pixels. */
Point pt = new Point((double)m_touchPoint.X, (double)m_touchPoint.Y);
SKPoint newPoint = ConvertToPixel(pt);
// Loop over the touch point start, then go to a certain value (like x + 100) just to get a "block" that's been altered for pixels.
for (int x = (int)newPoint.X; x < (int)newPoint.X + 200.0f; ++x)
{
for (int y = (int)newPoint.Y; y < (int)newPoint.Y + 200.0f; ++y)
{
// According to the x and y, change the color.
m_editedBm.SetPixel(x, y, color);
}
}
return m_editedBm;
}
}
Here I'm THINKING that it'll start, you know, at the coordinate I pressed (and these coordinates have been confirmed to be within the range of the image thanks to the function "TouchImage". And when it does get the correct coordinates (or at least it SHOULD of done that) the square will be drawn one "line" at a time. I have a game programming background so this kind of sounds simple but I can't believe I didn't get this right the first time.
Also I have another function, it MIGHT prove worthwhile because the original image is rotated and then put on screen. Why? Well by default the image, after taking the picture, and then displayed, is rotated to the left. I had no idea why but I corrected it with the following function:
// Just rotate the image because for some reason it's titled 90 degrees to the left.
public static SKBitmap Rotate()
{
using (var bitmap = m_bm)
{
// The new ones width IS the old ones height.
var rotated = new SKBitmap(bitmap.Height, bitmap.Width);
using (var surface = new SKCanvas(rotated))
{
surface.Translate(rotated.Width, 0.0f);
surface.RotateDegrees(90);
surface.DrawBitmap(bitmap, 0, 0);
}
return rotated;
}
}
I'll keep reading and looking up stuff on what I'm doing wrong, but if any help is given I'm grateful.
I'm having trouble counting the points of my AI Paddle Player and the user Paddle Player each time they miss colliding the ball. I tried using a boolean method and loops to count points, but to no avail as the program keeps on stating that the label in which my points are to be displayed, is "stack overflowed" due to an infinite loop which I am unable to recognize. So can you please help me.
Here is my code:
private void resetShiruken()
{
if ((reset == false) && (AIPoints < MAXPoints))
{
picShiruken.Location = new Point(ClientSize.Width / 2 - picShiruken.Width / 2, ClientSize.Height / 2 - picShiruken.Height / 2); //puts the picShiruken Picturebox in the middle anytime the picPlayerPaddle or picAIPaddle miss and is useful for counting the points of the computer and the player.
reset = true;
}
TheWinner();
}
private void TheWinner()
{
while (reset == true)
{
AIPoints += 1;
reset = false;
}
if (AIPoints >= AIPoints)
{
lblAIPoints.Text = AIPoints.ToString();
}
resetShiruken();
}
. resetShiruken() is a method that resets the ball to the middle each time any player misses.
. TheWinner() is a method that determines the winner of the game after either one of the players reach 5 points.
Thank You very Much,
Kai
As comments says:
you have recursive call of methods which lasts forever. resetShiruken calls TheWinner which calls resetShiruken and so on....
private void resetShiruken()
{
if ((reset == false) && (AIPoints < MAXPoints))
{
picShiruken.Location = new Point(ClientSize.Width / 2 - picShiruken.Width / 2, ClientSize.Height / 2 - picShiruken.Height / 2); //puts the picShiruken Picturebox in the middle anytime the picPlayerPaddle or picAIPaddle miss and is useful for counting the points of the computer and the player.
reset = true;
}
TheWinner();
}
private void TheWinner()
{
while (reset == true)
{
AIPoints += 1;
reset = false;
}
if (AIPoints >= AIPoints)
{
lblAIPoints.Text = AIPoints.ToString();
}
//resetShiruken(); <--- if you get rid of that call, should be fine.
}
Please note if you need to do reset after show the Winner, you should add additional parameter to reset method, or create separated method for post-winning reset.
Regular answer:
Your functions keep calling each other. Thats probably where the overflow is coming from.
From the functions given I'm unable to deduce exactly what your program flow would look like so I can't provide you with a fix which will do what you expect it to do.
To get rid of the overflow however simply remove either the call to "TheWinner" from "resetShiruken" or remove the call to "resetShiruken" from "TheWineer".
I suspect you should remove the latter.
Code style:
You're mixing upercase and lowercase function names which is a bad habit.
The default case for method names is "PascalCase" in c# (as depicted by the documentation).
I am doing a project called user initiated real time object tracking system. Here, is what I want to happen in the project:
1) Take a continuous stream from a web camera.
2) Using the mouse a user can draw a square, around an object of interest.
3) Then from there onwards, the square moves along with the object of interest. Thereby, tracking each and every place the object moves hence the term object tracking.
Current Progress
I have used dshownet(.NET wrapper for DirectShow)to take input from the web camera. And I am in the process of splitting the video to frames. I have 4 ways in mind to do the project :
Technique 1
There is a saved video
I load it.
when the video is running, i pause (using a pause button) it, at a particular scene and draw a square on an object.
And when i press play button the square will move along with the object with no/5 seconds processing time [OR] I will give the application some processing time(e.g. 3 minutes) and then it will play from that point onwards with the tracking taking place.
Technique 2
There is a saved video
I load it.
when the video is running i dont pause it but quickly draw a square on an object (when the object is still at some point).
Then the object will be tracked after that with no processing time. [OR] with some processing time (10 sec delay) making the file to play for a little greater time.
Technique 3
I take an input from a web cam for 1 min.
Save that video to a file
And perform Way 1 or Way 2
Technique 4 - (Apparently this seems alot harder)
Take input from a web cam continuously
Draw a square around the object without any pausing, when the object shows no movement (for e.g. when a person is sitting down on a chair)
And then show the tracking by moving the square along with the object with no processing time [OR] slight processing time of 2 secs such that the delay is not significantly apparent.
Objects to track :-
Basically I can track anything, since I use the mouse to draw
I am planning to use the whole body (but if this is troublesome.. next option)
I would try to track the face of an individual (obviously by drawing the area with a mouse.)
Time to code : 1 and 1/2 months
Progress : Still getting errors with the splitting. (Someone suggested to start splitting a saved video first, and I am in the process of trying that now)
MY QUESTIONS
1) Which Technique (out of the four) could I possibly implement in 1 and 1/2 months time frame ?
2) To code, is java + some java framework good for this or C#.net with emgucv/AForge.net/Dshownet [by the way my knowledge in java is good and not so good in C#.net]??
Thanks in advance
Technique 1,2,3 you could implement in Java using the Java Media Framework and ImageJ libraries. For Technique 4 you are better to implement in C++ or other non-interpreted language given the time constraints.
This example basically implements what you mentioned as Technique 4. The user draws a rect around a pattern or object to be tracked. In this case the tracked element is used to control the paddle in the Pong Game. Therefore the user can use objects to play the game in front of camera.
I think it solves the most parts of your problem.
Screenshot:
Source code:
package video.trackingPong;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import marvin.gui.MarvinImagePanel;
import marvin.image.MarvinImage;
import marvin.image.MarvinImageMask;
import marvin.io.MarvinImageIO;
import marvin.plugin.MarvinImagePlugin;
import marvin.util.MarvinAttributes;
import marvin.util.MarvinPluginLoader;
import marvin.video.MarvinJavaCVAdapter;
import marvin.video.MarvinVideoInterface;
import marvin.video.MarvinVideoInterfaceException;
public class TrackingPong extends JFrame implements Runnable{
private final static int BALL_INITIAL_PX=100;
private final static int BALL_INITIAL_PY=100;
private final static int BALL_INITIAL_SPEED=3;
private MarvinVideoInterface videoInterface;
private MarvinImagePanel videoPanel;
private Thread thread;
private MarvinImage imageIn,
imageOut;
private JPanel panelSlider;
private JSlider sliderSensibility;
private JLabel labelSlider;
private int regionPx,
regionPy,
regionWidth,
regionHeight;
private boolean regionSelected=false;
private int[] arrInitialRegion;
private int sensibility=30;
// Pong Game Attributes
private double ballPx=BALL_INITIAL_PX,
ballPy=BALL_INITIAL_PY;
private int ballSide=15;
double ballIncX=5;
private double ballIncY=5;
private int imageWidth,
imageHeight;
private Paddle paddlePlayer,
paddleComputer;
private int playerPoints=0,
computerPoints=0;
private MarvinImagePlugin findColorPattern,
flip,
text;
private MarvinImage imageBall,
imagePaddlePlayer,
imagePaddleComputer;
private MarvinAttributes attributesOut;
public TrackingPong(){
videoPanel = new MarvinImagePanel();
try{
// 1. Connect to the camera device.
videoInterface = new MarvinJavaCVAdapter();
videoInterface.connect(0);
imageWidth = videoInterface.getImageWidth();
imageHeight = videoInterface.getImageHeight();
imageOut = new MarvinImage(imageWidth, imageHeight);
// 2. Load Graphical Interface.
loadGUI();
// 3. Load and set up Marvin plug-ins.
findColorPattern = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.pattern.findColorPattern");
flip = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.transform.flip");
text = MarvinPluginLoader.loadImagePlugin("org.marvinproject.image.render.text");
text.setAttribute("fontFile", MarvinImageIO.loadImage("./res/font.png"));
text.setAttribute("color", 0xFFFFFFFF);
// 3. Load game images
imageBall = MarvinImageIO.loadImage("./res/ball.png");
imagePaddlePlayer = MarvinImageIO.loadImage("./res/paddleA.png");
imagePaddleComputer = MarvinImageIO.loadImage("./res/paddleB.png");
attributesOut = new MarvinAttributes(null);
// Set up plater and computer paddle properties.
paddlePlayer = new Paddle();
paddlePlayer.px=100;
paddlePlayer.py=420;
paddlePlayer.width=100;
paddlePlayer.height=30;
paddleComputer = new Paddle();
paddleComputer.px=100;
paddleComputer.py=30;
paddleComputer.width=100;
paddleComputer.height=30;
thread = new Thread(this);
thread.start();
}
catch(MarvinVideoInterfaceException e){
e.printStackTrace();
}
}
private void loadGUI(){
setTitle("Video Sample - Tracking Pong");
videoPanel.addMouseListener(new MouseHandler());
sliderSensibility = new JSlider(JSlider.HORIZONTAL, 0, 60, 30);
sliderSensibility.setMinorTickSpacing(2);
sliderSensibility.setPaintTicks(true);
sliderSensibility.addChangeListener(new SliderHandler());
labelSlider = new JLabel("Sensibility");
panelSlider = new JPanel();
panelSlider.add(labelSlider);
panelSlider.add(sliderSensibility);
Container container = getContentPane();
container.setLayout(new BorderLayout());
container.add(videoPanel, BorderLayout.NORTH);
container.add(panelSlider, BorderLayout.SOUTH);
setSize(videoInterface.getImageWidth()+20,videoInterface.getImageHeight()+100);
setVisible(true);
}
public void run(){
long time = System.currentTimeMillis();
int ticks=0;
// The game loop.
try{
while(true){
ticks++;
if(System.currentTimeMillis() - time > 1000){
System.out.println("FPS: "+ticks+" ");
ticks=0;
time = System.currentTimeMillis();
}
// 1. Get the current video frame.
imageIn = videoInterface.getFrame();
MarvinImage.copyColorArray(imageIn, imageOut);
// 2. Flip the frame horizontally so the player will see him on the screen like looking at the mirror.
flip.process(imageOut, imageOut);
if(regionSelected){
// 3. Find the player paddle position.
findColorPattern.setAttribute("differenceColorRange", sensibility);
findColorPattern.process(imageOut, imageOut, attributesOut, MarvinImageMask.NULL_MASK, false);
regionPx = (Integer)attributesOut.get("regionPx");
regionPy = (Integer)attributesOut.get("regionPy");
regionWidth = (Integer)attributesOut.get("regionWidth");
regionHeight = (Integer)attributesOut.get("regionHeight");
// 4. Invoke the game logic
pongGame();
// 5. Draw the detected region
imageOut.drawRect(regionPx, regionPy, regionWidth, regionHeight, Color.red);
// 6. Draw the player and computer points.
text.setAttribute("x", 105);
text.setAttribute("y", 3);
text.setAttribute("text", "PLAYER:"+playerPoints);
text.process(imageOut, imageOut);
text.setAttribute("x", 105);
text.setAttribute("y", 460);
text.setAttribute("text", "COMPUTER:"+computerPoints);
text.process(imageOut, imageOut);
}
videoPanel.setImage(imageOut);
}
}
catch(MarvinVideoInterfaceException e){
e.printStackTrace();
}
}
private void pongGame(){
// 1. Move the ball
ballIncX*=1.001;
ballIncY*=1.001;
ballPx+=ballIncX;
ballPy+=ballIncY;
// 2. Set the player paddle position to the the coordinates of the detected region.
paddlePlayer.px = regionPx+((regionWidth-paddlePlayer.width)/2);
// 3. Invoke simple computer AI
computerAI();
// 4. Check object positions and collisions.
checkPaddlePosition(paddlePlayer);
checkPaddlePosition(paddleComputer);
collisionScreen();
collisionTap();
// 5. Draw the game elements.
imageOut.fillRect(horizontalMargin, 0, 5, imageHeight, Color.black);
imageOut.fillRect(imageWidth-horizontalMargin, 0, 5, imageHeight, Color.black);
combineImage(imagePaddlePlayer, paddlePlayer.px, paddlePlayer.py);
combineImage(imagePaddleComputer, paddleComputer.px, paddleComputer.py);
combineImage(imageBall,(int)ballPx, (int)ballPy);
}
private void checkPaddlePosition(Paddle a_paddle){
if(a_paddle.px < horizontalMargin){
a_paddle.px = horizontalMargin;
}
if(a_paddle.px+a_paddle.width > imageWidth-horizontalMargin){
a_paddle.px = imageWidth-horizontalMargin-a_paddle.width;
}
}
private void computerAI(){
if(ballPx < paddleComputer.px+(paddleComputer.width/2)-10){
paddleComputer.px-=4;
}
if(ballPx > paddleComputer.px+(paddleComputer.width/2)+10){
paddleComputer.px+=4;
}
}
private int horizontalMargin = 100;
private void collisionScreen(){
if(ballPx < horizontalMargin){
ballPx = horizontalMargin;
ballIncX*=-1;
}
if(ballPx+ballSide >= imageWidth-horizontalMargin){
ballPx=(imageWidth-horizontalMargin)-ballSide;
ballIncX*=-1;
}
if(ballPy < 0){
playerPoints++;
ballPx = BALL_INITIAL_PX;
ballPy = BALL_INITIAL_PY;
ballIncY=BALL_INITIAL_SPEED;
ballIncX=BALL_INITIAL_SPEED;
} else if(ballPy+ballSide >= imageHeight){
computerPoints++;
ballPx = BALL_INITIAL_PX;
ballPy = BALL_INITIAL_PY;
ballIncY=BALL_INITIAL_SPEED;
ballIncX=BALL_INITIAL_SPEED;
}
}
private void collisionTap(){
if(ballCollisionTap(paddlePlayer)){
ballIncY*=-1;
ballPy = paddlePlayer.py-ballSide;
}
if(ballCollisionTap(paddleComputer)){
ballIncY*=-1;
ballPy = paddleComputer.py+paddleComputer.height;
}
}
private boolean ballCollisionTap(Paddle a_tap){
if
(
(
ballPx >= a_tap.px && ballPx <= a_tap.px+a_tap.width ||
ballPx <= a_tap.px && ballPx+ballSide >= a_tap.px
)
&&
(
ballPy >= a_tap.py && ballPy <= a_tap.py+a_tap.height ||
ballPy <= a_tap.py && ballPy+ballSide >= a_tap.py
)
)
{
return true;
}
return false;
}
private void combineImage(MarvinImage img, int x, int y){
int rgb;
int width = img.getWidth();
int height = img.getHeight();
for(int iy=0; iy<height; iy++){
for(int ix=0; ix<width; ix++){
if
(
ix+x > 0 && ix+x < imageWidth &&
iy+y > 0 && iy+y < imageHeight
)
{
rgb=img.getIntColor(ix, iy);
if(rgb != 0xFFFFFFFF){
imageOut.setIntColor(ix+x, iy+y, rgb);
}
}
}
}
}
public static void main(String args[]){
TrackingPong trackingPong = new TrackingPong();
trackingPong.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private class SliderHandler implements ChangeListener{
public void stateChanged(ChangeEvent a_event){
sensibility = (60-sliderSensibility.getValue());
}
}
private class MouseHandler implements MouseListener{
public void mouseEntered(MouseEvent a_event){}
public void mouseExited(MouseEvent a_event){}
public void mousePressed(MouseEvent a_event){}
public void mouseClicked(MouseEvent a_event){}
public void mouseReleased(MouseEvent event){
if(!regionSelected){
if(arrInitialRegion == null){
arrInitialRegion = new int[]{event.getX(), event.getY(),0,0};
}
else{
arrInitialRegion[2] = event.getX()-arrInitialRegion[0];
arrInitialRegion[3] = event.getY()-arrInitialRegion[1];
findColorPattern.setAttribute("regionPx", arrInitialRegion[0]);
findColorPattern.setAttribute("regionPy", arrInitialRegion[1]);
findColorPattern.setAttribute("regionWidth", arrInitialRegion[2]);
findColorPattern.setAttribute("regionHeight", arrInitialRegion[3]);
regionSelected = true;
}
}
}
}
private class Paddle{
public int px,py,width,height;
}
}
This article fully explains an algorithm very similar to what you want, and the accompanying source code is here. You can see it in action in this video. The part you would need to add would be, when the user draws a box, to identify which objects (already found by the algorithm) the box is around, and then simply follow the object with that int ID throughout the frames (the algorithm correlates the objects frame-by-frame to know it is the same object throughout the video).
Disclaimer: I'm the author; but I do think this is very useful, and have successfully used the algorithm a lot myself.)
When it comes to commercial computer vision applications, OpenCV and the Point Cloud Library aka PCL are your best friends. And articles like the one linked explains how to use tools like OpenCV to accomplish full stack motion tracking. (The pure Java implementation shows how it works down to the individual pixels.)
Suppose I want every word starting with a # to generate an event on double click. For this I have implemented the following test code:
private bool IsChannel(Point position, out int start, out int end)
{
if (richTextBox1.Text.Length == 0)
{
start = end = -1;
return false;
}
int index = richTextBox1.GetCharIndexFromPosition(position);
int stop = index;
while (index >= 0 && richTextBox1.Text[index] != '#')
{
if (richTextBox1.Text[index] == ' ')
{
break;
}
--index;
}
if (index < 0 || richTextBox1.Text[index] != '#')
{
start = end = -1;
return false;
}
while (stop < richTextBox1.Text.Length && richTextBox1.Text[stop] != ' ')
{
++stop;
}
--stop;
start = index;
end = stop;
return true;
}
private void richTextBox1_MouseMove(object sender, MouseEventArgs e)
{
textBox1.Text = richTextBox1.GetCharIndexFromPosition(new Point(e.X, e.Y)).ToString();
int d1, d2;
if (IsChannel(new Point(e.X, e.Y), out d1, out d2) == true)
{
if (richTextBox1.Cursor != Cursors.Hand)
{
richTextBox1.Cursor = Cursors.Hand;
}
}
else
{
richTextBox1.Cursor = Cursors.Arrow;
}
}
This handles detecting words that start with # and making the mouse cursor a hand when it hovers over them. However, I have the following two problems:
If I try to implement a double click event for richTextBox1, I can detect when a word is clicked, however that word is highlighted (selected), which I'd like to avoid. I can deselect it programmatically by selecting the end of the text, but that causes a flicker, which I would like to avoid. What ways are there to do this?
The GetCharIndexFromPosition method returns the index of the character that is closest to the cursor. This means that if the only thing my RichTextBox contains is a word starting with a # then the cursor will be a hand no matter where on the rich text control it is. How can I make it so that it is only a hand when it hovers over an actual word or character that is part of a word I'm interested in? The implemented URL detection also partially suffers from this problem. If I enable detection of URLs and only write www.test.com in the rich text editor, the cursor will be a hand as long as it is on or below the link. It will not be a hand if it's to the right of the link however. I'm fine even with this functionality if making the cursor a hand if and only if it's on the text proves to be too difficult.
I'm guessing I'll have to resort to some sort of Windows API calls, but I don't really know where to start.
I am using Visual Studio 2008 and I would like to implement this myself.
Update:
The flickering problem would be solved if I could make it so that no text is selectable through double clicking, only through dragging the mouse cursor and programmatically. Is this easier to achieve? Because I don't really care if one can select text or not by double clicking in this case.
On point (2) you could try:
After if (richTextBox1.Text.Length == 0){ ... }
//get the mouse point in client coordinates
Point clientPoint = richTextBox1.PointToClient(richTextBox1.PointToScreen(position));
int index = richTextBox1.GetCharIndexFromPosition(position);
//get the position of the closest char
Point charPoint = richTextBox1.GetPositionFromCharIndex(index);
bool notOnTheSameLine = ((clientPoint.Y < charPoint.Y) || (clientPoint.Y > charPoint.Y + richTextBox1.Font.Height));
bool passedTheWord = (clientPoint.X > charPoint.X + richTextBox1.Font.SizeInPoints);
if (notOnTheSameLine || passedTheWord)
{
start = end = -1;
return false;
}
For point (1) maybe have a different way of following the link than dbl-click? Maybe cntl-click would avoid the issues of the word becoming selected...
I'm trying to learn C# (so go easy on me!) and as part of a coursework I have been asked to create a game called strikeout.
The game rules are very simple. It is simply a case of knocking one counter into another, with the aim of leaving one counter left on the screen.
I am having trouble simply moving one counter into another. I have created a number of picture boxes using a loop (to make up my board), and created an event handler using the loop. How can I use the single even handler to move all of my counters?
{
(...)
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
int x = j * (75);
int y = i * (75);
pictureBoxCounter[j, i] = new PictureBox();
pictureBoxCounter[j, i].Location = new System.Drawing.Point(x, y);
pictureBoxCounter[j, i].Size = new System.Drawing.Size(75, 75);
pictureBoxCounter[j, i].BackColor = System.Drawing.Color.Transparent;
panelGame.Controls.Add(pictureBoxCounter[j, i]);
this.pictureBoxCounter[j, i].Click += new System.EventHandler(this.pictureBoxCounter_Click);
}
}
} // end of some function
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
//I need some code here but nothing seems to work :(
}
I've spent way to long on this problem. Even asked my tutor to help. Instead of helping he managed to break most of my code. So after fixing the problems he caused I am now back with a compiling program!
(When running the program you will need to enter player info to enable the start game button.)
If I can help with any other information don't hesitate to ask!
This programming problem appears intended to teach you about separation of concerns. If it isn't, it should be. (It is now!)
You have two problems, not one. (Actually, you have more than two, but you have two big ones.) One problem is: How do I create a set of objects representing the counters in this game, and what rules do those objects follow? The other problem is: How do I represent the counters on the screen?
If your solution to the first problem is to create a bunch of PictureBoxes, you're going down a rabbit hole that it's going to be tough to get back out of. You should solve the first problem first, and then the second problem.
Here's a rough sketch (very rough, because I don't know the rules of this game) of an object model that addresses the first problem:
public class Board
{
public const int Height = 8;
public const int Width = 8;
private Counters[Height][] Counters { get; set; }
public Counter GetCounter(int row, int col)
{
return Counters[row][col];
}
public void Initialize() { }
public void ExecuteMove(Counter c) { }
}
public class Counter
{
public int Row { get; set; }
public int Column { get; set; }
}
So, a Counter object knows where it is (its Row and Column). A Board object knows about all of the Counter objects, it knows how to find a Counter given its row and column, and it knows how to execute a move when a counter gets clicked on. (I don't know anything about the rules of the game, but they're going to live in the ExecuteMove method.
You can now trivially write a method that prints the board to the console:
public void PrintBoard(Board b)
{
for (int col = 0; col < board.Width; col ++)
{
for (int row = 0, row < board.Height; row++)
{
Counter c = board.GetCounter[row][col];
Console.Write(c == null ? " " : "*");
}
Console.WriteLine();
}
}
and a method to input a move:
public Counter InputMove(Board b)
{
string s;
Console.Write("Row: ");
s = Console.ReadLine();
int row = Convert.ToInt32(s);
if (s == "") return null;
Console.Write("Column: ");
s = Console.ReadLine();
if (s == "") return null;
int column = Convert.ToInt32(s);
return b.GetCounter(row, column);
}
...and now you have everything you need in order to code and test the ExecuteMove method. Get that working. I'll wait.
You done? Good. There are a couple of problems that you probably ran into that I haven't addressed. For instance, you probably discovered that when you move a Counter, you have to update both the board's array and the Counter itself. And you also probably discovered that you have to come up with some way of keeping track of what happens when a Counter is destroyed. Aren't you glad you weren't screwing around with mouse clicks and UI elements too?
Now for part two. Let's make a UI that knows how to talk to the Board. In your form, loop through the counters in the board, create a PictureBox for each (with its position based on the Row and Column properties), and add it to a Dictionary<PictureBox, Counter> called, say, Counters. You'll also want a Dictionary<Counter, PictureBox> called, say, PictureBoxes. These maps give you the ability to find a Counter given its PictureBox, and vice versa.
Attach this event handler to each PictureBox:
private void PictureBox_Click(object sender, EventArgs e)
{
PictureBox p = (PictureBox) sender;
Counter c = Counters[p];
Board.ExecuteMove(c);
}
(This, by the way, is the answer to your original question.)
Well, that's all well and good: you can click on a PictureBox, find its Counter, and execute the move. But how do you update the visual state of the game in the UI? That depends, a lot, on what the actual rules of the game are.
For instance, if clicking on a counter makes it disappear, you might add a Visible property to the Counter class and have the Board class's ExecuteMove update it. If, when a Counter becomes invisible, the other Counters in its row move left or right, the ExecuteMove method will have to update their Row and Column, and do something with the now-invisible Counter that was at that position.
But you already worked all of this out back when you tested all of the game logic, right? All you need to implement in your UI is an equivalent method to the one you built for printing it to the console. Only this one iterates through all of the Counters in the board, and updates their PictureBox based on their state.
There are a lot of subtleties that I'm glossing over here, mostly because a) I don't fully understand your problem and b) I'm not actually trying to do your homework for you.
You'll need to find the clicked picture box back in the Click event handler. You can use the sender argument for that. Trivially:
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
PictureBox box = sender as PictureBox;
box.BackColor = System.Drawing.Color.Coral;
}
But you'll need to implement your game logic as well, which will require you know the box's location on the game board. You could use the PictureBox.Tag property for that. It could store a Point for example:
...
pictureBoxCounter[j, i].BackColor = System.Drawing.Color.Transparent;
pictureBoxCounter[j, i].Tag = new Point(j, i);
panelGame.Controls.Add(pictureBoxCounter[j, i]);
...
private void pictureBoxCounter_Click(object sender, EventArgs e)
{
PictureBox box = sender as PictureBox;
box.BackColor = System.Drawing.Color.Coral;
Point pos = (Point)box.Tag;
int row = pos.X;
int col = pos.Y;
//etc...
}
But you probably want to use a helper class that stores more info than just the row and column number.