10

Right now I have this code:

int number = 0;
DirectoryInfo di = new DirectoryInfo(scpath + @"Screenshots\");

if (di.Exists) {

} else {
    di.Create();
}
int screenWidth = Screen.GetBounds(new Point(0, 0)).Width;
int screenHeight = Screen.GetBounds(new Point(0, 0)).Height;
Bitmap bmpScreenShot = new Bitmap(screenWidth, screenHeight);
Graphics gfx = Graphics.FromImage((Image)bmpScreenShot);
gfx.CopyFromScreen(0, 0, 0, 0, new Size(screenWidth, screenHeight));
bmpScreenShot.Save(di + "Screenshot_" + number, ImageFormat.Jpeg);

Program takes a screenshot (which works) and saves it. What I want to do is to have the program check and see if a screenshot exists ("Screenshot_*") and to create it if it doesn't. If it does, increment file name till it hits a number that hasn't been used at the end of "Screenshot_" Not sure how to go about this given that it's more with files and incrementing. I'm thinking about a for loop but I'm playing with it now.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
David Brewer
  • 1,864
  • 8
  • 25
  • 37
  • 4
    This is just a consideration but putting race conditions for writing aside a better solution might be to use the current date/time for each save. – Quintin Robinson Apr 12 '12 at 20:56
  • Hmm, i hadn't thought of sir. I'll look into a solution like that. All i need is a way to save them because the other section of the program backs them up to a folder for that day. so a time stamp in the file name could be a good way to go. – David Brewer Apr 12 '12 at 20:57
  • Whatever method you choose, make sure that you use a format where they will sort in order of time when sorted by filename. So if you use a date/time format make sure to use something like '2012-04-12-13-59-00' or similar. – thelsdj Apr 12 '12 at 20:59
  • I just want to point out that this is bad idea since it will have to check for screenshots on HDD and it takes unnecessary time. You really should do what Quintin suggested and use timestring, like UNIX timestamp for screenshot filenames. – Stan Apr 12 '12 at 21:00
  • @Qmal - re *and it takes unnecessary time*. It's a screenshot app. The time it takes to do stats on the filesystem is going to be small, on NTFS. The time cost is irrelevant for his purposes. This is not a realtime system. – Cheeso Apr 12 '12 at 21:07

9 Answers9

12

Getting the name of a file that does not exist sounds like a job for a method.

string IndexedFilename(string stub, string extension) 
{
    int ix = 0;
    string filename = null;
    do {
        ix++;
        filename = String.Format("{0}{1}.{2}", stub, ix, extension);
    } while (File.Exists(filename));
    return filename;
}

There is a race condition if you call this from multiple threads. Assuming you have just one app and one thread in the app asking for filenames, then this ought to work.

The code to use the method looks like this:

string di = Path.Combine(scpath, "Screenshots");
if (!Directory.Exists(di) { 
    Directory.Create(di); 
} 
int screenWidth = Screen.GetBounds(new Point(0, 0)).Width; 
int screenHeight = Screen.GetBounds(new Point(0, 0)).Height; 
Bitmap bmpScreenShot = new Bitmap(screenWidth, screenHeight); 
Graphics gfx = Graphics.FromImage((Image)bmpScreenShot); 
gfx.CopyFromScreen(0, 0, 0, 0, new Size(screenWidth, screenHeight));
string filename = IndexedFilename(Path.Combine(di,"Shot_"),"jpg");
bmpScreenShot.Save(filename, ImageFormat.Jpeg); 
Cheeso
  • 189,189
  • 101
  • 473
  • 713
9

Like @Quintin said, use datetime for filename:

string filename = Path.Combine(
    di.FullName,
    String.Format("{0}.jpg", DateTime.Now.ToString("yyyy-MM-dd HH.mm.ss")));
bmpScreenShot.Save(filename, ImageFormat.Jpeg);
Marco
  • 56,740
  • 14
  • 129
  • 152
  • I actually ended up using this code. :D Thank you sir. After hearing what Quintin said it makes more sense that way. :) – David Brewer Apr 13 '12 at 14:03
4

This is a possibility

string[] files = System.IO.Directory.GetFiles(scpath, "Screenshot_*.jpg");
string baseName = Path.Combine(scpath, "Screenshot_");
string filename;
int i = 0;
do {
    filename = baseName + ++i + ".jpg";
} while (files.Contains(filename));

The advantage of this approach is that the file system is queried only once. If the file number gets large, consider adding the file names to a hash set to speed up the checks even more:

var files = new HashSet<string>(Directory.GetFiles(scpath, "Screenshot_*.jpg"));
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • thank you :) for this right & simple working solution :) why no one mention this solution. there is just a mistake in condition. we should use `File.Exist` instead, or we should insert just filenames in `files` array not full paths. – ACE Sep 04 '16 at 13:16
2

Instead of using a number as a way to differentiate between screenshots use a timestamp:

string currentDT = string.Format("{0:D4}.{1:D2}.{2:D2}-{3:D2}.{4:D2}.{5:D2}",
                   DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day,
                   DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second)
bmpScreenShot.Save(di + "Screenshot_" + currentDT, ImageFormat.Jpeg); 
Steve
  • 213,761
  • 22
  • 232
  • 286
2

I'd use GUID...

try{
    bmpScreenShot.Save(di + "Screenshot_" + Guid.NewGuid().ToString(), ImageFormat.Jpeg);
}catch(Exception e)
{ 
    //handle the problems here, for example if file already exists, try again
}

This should work well until you run out of unique GUIDs...

m0s
  • 4,250
  • 9
  • 41
  • 64
1
public static string MakeUniqueFileName(string file)
{
    string dir = Path.GetDirectoryName(file);
    string fn;

    for (int i = 0; ; ++i)
    {
        fn = Path.Combine(dir, string.Format(file, i));

        if (!File.Exists(fn))
            return fn;
    }
}

Use it like this:

string file = scpath + @"Screenshots\" + "Screenshot_{0}.png";
bmpScreenShot.Save(MakeUniqueFileName(file), ImageFormat.Jpeg);
clumpter
  • 1,898
  • 6
  • 27
  • 38
0

This will create output_0.jpg output_1.jpg ... output_n.jpg:

int filecount = 0;
string path = Environment.CurrentDirectory;
for (int i = 0; File.Exists(path + @"\output_" + i + ".jpg"); i++)
{
    filecount = i + 1;
}
File.Create(path + @"\output_" + filecount + ".jpg");
pushkin
  • 9,575
  • 15
  • 51
  • 95
isfot
  • 1
0
private static string GetUniqueFile(string path, string file, string ext)
{
    int filecount = 0;
    int i = 0;
    for (i = 0; File.Exists(path + "\\" + file + "_" + i + "." + ext); i++)
    {
        filecount = i + 1;
    }

    return path + "\\" + file + "_" + i.ToString() + "." + ext;
}
chan
  • 9
  • 3
0

In case the directory with the screenshots contains many images, it might be beneficial to find the next available filename using binary search. This way the File.Exists method will be called far fewer times than doing an incremental search.

/// <summary>
/// Performs a binary search in the Int32 range, and returns the first element
/// that satisfies a condition.
/// </summary>
public static TElement BinarySearchFirst<TElement>(
    Func<int, TElement> selector,
    Predicate<TElement> predicate,
    int start = 1)
{
    ArgumentNullException.ThrowIfNull(selector);
    ArgumentNullException.ThrowIfNull(predicate);
    if (start < 0) throw new ArgumentOutOfRangeException(nameof(start));

    long lo = start;
    long hi = 1;
    (TElement Value, bool HasValue) maxFound = default;

    // First stage, find an approximate upper limit of the search space.
    while (hi < Int32.MaxValue)
    {
        hi = Math.Min(hi * 10, Int32.MaxValue);
        if (hi < start) continue;
        TElement item = selector((int)hi);
        bool accepted = predicate(item);
        if (accepted)
        {
            maxFound = (item, true);
            hi--;
            break;
        }
        lo = hi + 1;
    }

    // Second stage, perform binary search between lo and hi.
    while (lo <= hi)
    {
        long mid = lo + ((hi - lo) >> 1);
        TElement item = selector((int)mid);
        bool accepted = predicate(item);
        if (accepted)
        {
            maxFound = (item, true);
            hi = mid - 1;
        }
        else
            lo = mid + 1;
    }

    if (maxFound.HasValue) return maxFound.Value;
    throw new InvalidOperationException("Element not found in the Int32 range.");
}

Usage example:

string pathFound = BinarySearchFirst(
    i => Path.Combine(@"C:\Screenshots", $"Screenshot-{i}.png"),
    path => !File.Exists(path));

In a folder with 200 screenshots, the above code will check for the existence of the files below:

Screenshot-10.png
Screenshot-100.png
Screenshot-1000.png
Screenshot-550.png
Screenshot-325.png
Screenshot-212.png
Screenshot-156.png
Screenshot-184.png
Screenshot-198.png
Screenshot-205.png
Screenshot-201.png
Screenshot-199.png
Screenshot-200.png

...before returning the value "C:\Screenshots\Screenshot-201.png" as the result.

Online demo.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104