I'm using the following code to download the source code of an HTML page
String search = $"<search url>";
String result = "";
using (WebClient wc = new WebClient())
{
var searchURL = new Uri(search);
wc.DownloadStringCompleted += (s, e) =>
{
result = e.Result;
};
wc.DownloadStringAsync(searchURL);
}
return result.Trim();
This exists in a static method
public static Scrape(String arg)
of a class Scraper.
However, when I do
txtResult.Text = Scraper.Scrape(arg);
in the code behing of a form, nothing appears. Using breakpoints, I see that e.Result indeed contains the expected data, but something happens and the data is "lost" in the way. Anyone has any idea what I'm missing;
Because the download is asynchronous it hasn't updated the value of result before you reach the line return "result.Trim()" as a result you are returning the empty string.
You need to wait for WC to complete, if you use the task method then accessing the result property will cause you to wait for the response.
String search = $"<search url>";
String result = "";
using (WebClient wc = new WebClient())
{
var searchURL = new Uri(search);
result = wc.DownloadStringTaskAsync(searchURL).Result;
}
return result.Trim();
However if you do this in the current method as it stands you will end up hanging your UI as the wait happens on the foreground thread.
You can move the update to the background by changing your call to use a task with a continuation.
Instead of
txtResult.Text = Scraper.Scrape(arg);
Add a using using System.Threading.Tasks;
then you can use the line
Task.Run(() => Scraper.Scrape(arg))
.ContinueWith(t => txtResult.Text = t.Result,
TaskScheduler.FromCurrentSynchronizationContext());
You may also want to precede this with a line txtResult.Text = "Please Wait Fetching....";
Related
I have a viewmodel whose constructor leads to some asynchronous calls and I'm having trouble testing their result.
public ExamAcquireImageViewModel(ICore coreInstance, ExamManager examManager, Action cancelHandler) : base(examManager)
{
TemplateVm.OnSelectionChanged += StartAcquiringImages;
// BECAUSE OF PREVIOUS LINE, THIS CALLS StartAcquiringImages()
SelectedLocationInTemplate = SelectedLocationInTemplate ?? FindNextLowest();
}
private void StartAcquiringImages(LocationViewModel nextLocation = null)
{
new Thread(() =>
{
// expensive operation
_recon = _coreInstance.AcquireImageSet(Exam, SelectedLocationInTemplate.LocationModel);
int width = 1000;
int height = 1000;
// (less) expensive operation
AcquiredImage = _recon?.RenderImageAndGetCurrentFrame().ToWriteableBitmap(width, height);
SelectedLocationInTemplate = GetNextLocation();
}).Start();
}
The constructor assigns the OnSelectionChanged and then changes the selection, setting off the image acquisition process. I want to test that AcquireImages has been assigned to.
public void TestAcquisition()
{
ExamAcquireImageViewModel acqVm = new ExamAcquireImageViewModel(mockCore.Object, examManager, () => { });
Assert.IsNotNull(acqVm.AcquiredImage);
}
I have all my Moqs set up correctly. However because of the threaded/asynchronous operations, the test fails because the assertion runs before any AcquiredImage gets set (indeed, I imagine, before anything in the new Thread gets run).
I've tried ExamAcquireImageViewModel acqVm = await new ExamAcquireImageViewModel(mockCore.Object, examManager, () => { }); but that doesn't compile (no GetAwaiter etc).
How do I wait for this thread in my tests?
I'll also want to test that the SelectedLocationInTemplate "increments" automatically and each next image gets acquired (see last line in the Thread). I don't know where I'd intercept or "peek into" the whole process to see that happening.
For anyone who has a similar problem, I will answer my own question.
It's very very very simple. Thread.Sleep.
[TestMethod]
public void TestAcquisitionSucess()
{
ExamAcquireImageViewModel acqVm = GetAcqVm();
Thread.Sleep(30);
Assert.IsNotNull(acqVm.AcquiredImage);
}
I need to alter the text of a FlowDocument without changing the existing formatting and am having trouble doing so.
My thought was to do a foreach of Blocks in the document. Then for any Paragraph do a foreach of the Inlines like this;
foreach (var x in par.Inlines)
{
if (x.GetType() == typeof(Run))
{
Run r = (Run)x;
r.Text = r.Text.Replace("#", "$");
}
}
Problem is that this returns the following error message;
System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'
What is the correct way of doing this?
The usual solution is to call ToList() on the collection and iterate through the new collection returned by ToList().
var runs =
flowdoc.Blocks.OfType<Paragraph>()
.SelectMany(par => par.Inlines).OfType<Run>()
.ToList();
foreach (var r in runs)
{
r.Text = r.Text.Replace("#", "$");
}
Your error comes from trying to use the foreach loop to enumerate through a collection while also modifying the collection. Use a for loop.
For changing text in a flow document, try a TextPointer + TextRange, here's an example (this one changes the text background but you can change text just as easily).
private void ClearTextHighlight(FlowDocument haystack)
{
TextPointer text = haystack.ContentStart;
TextPointer tpnext = text.GetNextContextPosition(LogicalDirection.Forward);
while (tpnext != null){
TextRange txt = new TextRange(text, tpnext);
//access text via txt.Text
//apply changes like:
var backgroundProp = txt.GetPropertyValue(TextElement.BackgroundProperty) as SolidColorBrush;
if(backgroundProp != null && backgroundProp.Equals(Setting_HighlightColor)){
//change is here
txt.ApplyPropertyValue(TextElement.BackgroundProperty, Setting_DefaultColor);
}
text = tpnext;
tpnext = text.GetNextContextPosition(LogicalDirection.Forward);
}
}
I have a console application which generating rows line by line:
Data1
Data2
Data3
...
and when its over command line will be cleared, it reapeats infinitelly (the datas can change)
I have to watch the console application's command line with windows aplication real time and work for the lines data (for example save it to list ox line by line)! It is possible?
You basically need to subscribe to the output streams of that console application, to be able to get each line printed on the console.
What you need to do is to create the Windows Forms application (WPF would also work) and start the console application from there.
If you don't want to show your current console application as a visible window, remember to set CreateNoWindow to true.
Here's how to start the console application:
var processStartInfo = new ProcessStartInfo(fileName, arguments);
processStartInfo.UseShellExecute = false;
processStartInfo.ErrorDialog = false;
processStartInfo.RedirectStandardOutput = true; // We handle the output
processStartInfo.CreateNoWindow = true; // If you want to hide the console application so it only works in the background.
// Create the actual process
currentProcess = new Process();
currentProcess.EnableRaisingEvents = true;
currentProcess.StartInfo = processStartInfo;
// Start the process
bool processDidStart = currentProcess.Start();
We need a BackgroundWorker to read the output from the console application in the background.
outputReader = TextReader.Synchronized(currentProcess.StandardOutput);
outputWorker.RunWorkerAsync();
Now you're able to get all the output from the console application in real time and use it to create a list or whatever you want to.
void outputWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Work until cancelled
while (outputWorker.CancellationPending == false)
{
int count = 0;
char[] buffer = new char[1024];
do
{
StringBuilder builder = new StringBuilder();
// Read the data from the buffer and append to the StringBuilder
count = outputReader.Read(buffer, 0, 1024);
builder.Append(buffer, 0, count);
outputWorker.ReportProgress(0, new OutputEvent() { Output = builder.ToString() });
} while (count > 0);
}
}
The processed data is available through the ProgressChanged event of the BackgroundWorker.
void outputWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.UserState is OutputEvent)
{
var outputEvent = e.UserState as OutputEvent;
/* HERE YOU CAN USE THE OUTPUT LIKE THIS:
* outputEvent.Output
*
* FOR EXAMPLE:
* yourList.Add(outputEvent.Output);
*/
}
}
The above code is taken and modified to your purposes from the following Codeproject.com article in case it ceases to exist in the future: Embedding a Console in a C# Application.
hi im working on a project that uses invoke and threads.. it is a simple remote desktop program with chat.. i got a sample here on the internet in c# winform, but i would like to convert it to wpf.. i have no problem in sending message to another client using the wpf program but it cannot receive ( or cannot read) the sent messages from the others.. i think it has something to do with the thread and the invoke method, i read that wpf does invoke differently and i did try the dispatcher.invoke, but it still doesnt do the trick
pls hellp
here's the code
wait = new Thread(new ThreadStart(waitForData));
wait.Start();
that snippet above is executed when a successful connection is made in tcpclient
private void waitForData()
{
try
{
NetworkStream read = tcpclnt.GetStream();
while (read.CanRead)
{
byte[] buffer = new byte[64];
read.Read(buffer, 0, buffer.Length);
s = new ASCIIEncoding().GetString(buffer);
System.Console.WriteLine("Recieved data:" + new ASCIIEncoding().GetString(buffer));
rcvMsg = new ASCIIEncoding().GetString(buffer) + "\n";
hasNewData = true;
bool f = false;
f = rcvMsg.Contains("##");
bool comand = false;
comand = rcvMsg.Contains("*+*-");
/*File receive*/
if (f)
{
string d = "##";
rcvMsg = rcvMsg.TrimStart(d.ToCharArray());
int lastLt = rcvMsg.LastIndexOf("|");
rcvMsg = rcvMsg.Substring(0, lastLt);
NetworkStream ns = tcpclnt.GetStream();
if (ns.CanWrite)
{
string dataS = "^^Y";
byte[] bf = new ASCIIEncoding().GetBytes(dataS);
ns.Write(bf, 0, bf.Length);
ns.Flush();
}
try
{
new Recieve_File().recieve_file(rcvMsg);
}
catch (Exception ec)
{
System.Console.WriteLine(ec.Message);
}
}
/*Command-shutdown/restart/logoff*/
else if (comand)
{
string com = "*+*-";
rcvMsg = rcvMsg.TrimStart(com.ToCharArray());
execute_command(rcvMsg);
}
else
{
this.Invoke(new setOutput(setOut));
Thread.Sleep(1000);
}
}
}
catch (Exception ex)
{
wait.Abort();
output.Text += "Error..... " + ex.StackTrace;
}
}
the snippet above is a code that listens if there is a message or command.. the line
this.invoke(new setoutput(setout)) is a code for appending text in the rtb
hope someone could help me thanks
You've posted a lot of code, but I'm assuming it's only the call to Control.Invoke which is causing the problem. In WPF, use Dispatcher.Invoke (or Dispatcher.BeginInvoke) instead, via the Dispatcher property on the relevant UI element.
I'd also strongly encourage you to:
Refactor your code into smaller methods
Stop catching just Exception except at the top level of any large operation (it should just be a fall-back; usually you catch specific exceptions)
Start following .NET naming conventions
Add a using directive for System so you can just write Console.WriteLine instead of System.Console.WriteLine everywhere
Use Encoding.ASCII instead of creating a new ASCIIEncoding each time you need one
Use a StreamReader to read character data from a stream, instead of reading it as binary data first and then encoding it
For either Stream or TextReader, don't ignore the return value from Read - it tells you how many bytes or characters have been read
Is there a way to update the print queue status information contained in the PrintQueue object?
I've tried calling Refresh on the PrintQueue object but that doesn't really do anything. For instance, I've turned off the printer and the Control Panel correctly shows the printer as "Offline", however the QueueStatus property, as well as the IsOffline property don't reflect that - no matter how many times I call Refresh on both the PrintServer and the PrintQueue in question.
I've seen examples of how to get status information using WMI queries but I wonder - since these properties are available on the PrintQueue object - whether there is any way to use those.
After try to print your PrintDocument (System.Drawing.Printing), try to check status of printjobs.
First step:
Initialize your printDocument.
Second step:
Get your printer Name From System.Drawing.Printing.PrinterSettings.InstalledPrinters.Cast<string>();
And copy it into your printerDocument.PrinterSettings.PrinterName
Third step:
Try to print and dispose.
printerDocument.Print();
printerDocument.Dispose();
Last step: Run the check in a Task (do NOT block UI thread).
Task.Run(()=>{
if (!IsPrinterOk(printerDocument.PrinterSettings.PrinterName,checkTimeInMillisec))
{
// failed printing, do something...
}
});
Here is the implementation:
private bool IsPrinterOk(string name,int checkTimeInMillisec)
{
System.Collections.IList value = null;
do
{
//checkTimeInMillisec should be between 2000 and 5000
System.Threading.Thread.Sleep(checkTimeInMillisec);
using (System.Management.ManagementObjectSearcher searcher = new System.Management.ManagementObjectSearcher("SELECT * FROM Win32_PrintJob WHERE Name like '%" + name + "%'"))
{
value = null;
if (searcher.Get().Count == 0) // Number of pending document.
return true; // return because we haven't got any pending document.
else
{
foreach (System.Management.ManagementObject printer in searcher.Get())
{
value = printer.Properties.Cast<System.Management.PropertyData>().Where(p => p.Name.Equals("Status")).Select(p => p.Value).ToList();
break;
}
}
}
}
while (value.Contains("Printing") || value.Contains("UNKNOWN") || value.Contains("OK"));
return value.Contains("Error") ? false : true;
}
Good luck.