Showing posts with label devexpress. Show all posts
Showing posts with label devexpress. Show all posts

Monday, January 15, 2018

Find out which winforms controls are accessed from a background thread

Leave a Comment

We have built a huge winforms project, already in progress for multiple years.

Sometimes, our users get an exception which looks like this one.

The resolution of this problem seems to be:

don't acces UI components from a background thread

.

But since our project is a very big project with a lot of different threads, we don't succeed in finding all these.

Is there a way to check (with some tool or debugging option) which components are called from a background thread?

To clarify:

I created a sample winforms project with a single Form, containing two Button

public partial class Form1 : Form {     public Form1()     {         InitializeComponent();     }      private void button1_Click(object sender, EventArgs e)     {         button1.Text = "Clicked!";     }      private void button2_Click(object sender, EventArgs e)     {          Task.Run(() =>         {             button2.BackColor = Color.Red; //this does not throw an exception             //button2.Text = "Clicked"; //this throws an exception when uncommented         });     } } 

The background color of button2 is set to red when the button is clicked. This happens in a background thread (which is considered bad behavior). However, it doesn't (immediately) throw an exception. I would like a way to detect this as 'bad behavior'. Preferably by scanning my code, but if it's only possible by debugging, (so pausing as soon as a UI component is accessed from a background thread) it's also fine.

5 Answers

Answers 1

I've got 2 recommendations to use together, the first is a Visual Studio Plugin called DebugSingleThread.

You can freeze all the threads and work on one at a time (obviously the non-main-UI threads) and see each threads access to controls. Tedious I know but not so bad with the second method.


The second method is to get the steps in order to reproduce the problem. If you know the steps to reproduce it, it will be easier to see whats causing it. To do this I made this User Action Log project on Github.

It will record every action a user makes, you can read about it here on SO: User Activity Logging, Telemetry (and Variables in Global Exception Handlers).

I'd recommend you also log the Thread ID, then when you have been able to reproduce the problem, go to the end of the log and work out the exact steps. Its not as painful as it seems and its great for getting application telemetry.

You might be able to customise this project, eg trap a DataSource_Completed event or add a dummy DataSource property that sets the real Grids DataSource property and raises an INotifyPropertyChanged event - and if its a non-main thread ID then Debugger.Break();.


My gut feeling is you're changing a control's (eg a grid) data source in a background thread (for that non-freeze feel) and thats causing a problem with synchronisation. This is what happened to the other DevExpress customer who experienced this. Its discussed here in a different thread to the one you referenced.

Answers 2

Is your app set to ignore cross threading intentionally?

Cross-thread operations should be blowing up all the time in winforms. It checks for them like crazy in just about every method. for a starting point check out https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs.

Somewhere in your app, somebody might have put this line of code:

Control.CheckForIllegalCrossThreadCalls = False; 

Comment that out and run the app, then follow the exceptions.

(Usually you can fix the problem by wrapping the update in an invoke, e.g., in a worker thread if you see textbox1.text=SomeString; change it to `textbox.invoke(()=>{textbox1.text=SomeString;});.

You may also have to add checking for InvokeRequired, use BeginInvoke to avoid deadlocks, and return values from invoke, those are all separate topics.

this is assuming even a moderate refactor is out of the question which for even a medium sized enterprise app is almost always the case.

Note: it's not possible to guarantee successful discovery of this case thru static analysis (that is, without running the app). unless you can solve the halting problem ... https://cs.stackexchange.com/questions/63403/is-the-halting-problem-decidable-for-pure-programs-on-an-ideal-computer etc...

Answers 3

I did this to search for that specific situation but of course, need to adjust it to your needs, but the purpose of this is to give you at least a possibility.

I called this method SearchForThreads but since it's just an example, you can call it whatever you want.

The main idea here is perhaps adding this Method call to a base class and call it on the constructor, makes it somewhat more flexible.

Then use reflection to invoke this method on all classes deriving from this base, and throw an exception or something if it finds this situation in any class.

There's one pre req, that is the usage of Framework 4.5. This version of the framework added the CompilerServices attribute that gives us details about the Method's caller.

The documentation for this is here

With it we can open up the source file and dig into it.

What i did was just search for the situation you specified in your question, using rudimentary text search.

But it can give you an insight about how to do this on your solution, since i know very little about your solution, i can only work with the code you put on your post.

public static void SearchForThreads(         [System.Runtime.CompilerServices.CallerMemberName] string memberName = "",         [System.Runtime.CompilerServices.CallerFilePath] string sourceFilePath = "",         [System.Runtime.CompilerServices.CallerLineNumber] int sourceLineNumber = 0)         {             var startKey = "this.Controls.Add(";             var endKey = ")";              List<string> components = new List<string>();              var designerPath = sourceFilePath.Replace(".cs", ".Designer.cs");             if (File.Exists(designerPath))             {                 var designerText = File.ReadAllText(designerPath);                 var initSearchPos = designerText.IndexOf(startKey) + startKey.Length;                  do                 {                     var endSearchPos = designerText.IndexOf(endKey, initSearchPos);                     var componentName = designerText.Substring(initSearchPos, (endSearchPos - initSearchPos));                     componentName = componentName.Replace("this.", "");                     if (!components.Contains(componentName))                         components.Add(componentName);                  } while ((initSearchPos = designerText.IndexOf(startKey, initSearchPos) + startKey.Length) > startKey.Length);             }              if (components.Any())             {                 var classText = File.ReadAllText(sourceFilePath);                 var ThreadPos = classText.IndexOf("Task.Run");                 if (ThreadPos > -1)                 {                     do                     {                         var endThreadPos = classText.IndexOf("}", ThreadPos);                          if (endThreadPos > -1)                         {                             foreach (var component in components)                             {                                 var search = classText.IndexOf(component, ThreadPos);                                 if (search > -1 && search < endThreadPos)                                 {                                     Console.WriteLine($"Found a call to UI thread component at pos: {search}");                                 }                             }                         }                     }                     while ((ThreadPos = classText.IndexOf("Task.Run", ++ThreadPos)) < classText.Length && ThreadPos > 0);                 }             }         } 

I hope it helps you out.

You can get the Line number if you split the text so you can output it, but i didn't want to go through the trouble, since i don't know what would work for you.

string[] lines = classText.Replace("\r","").Split('\n'); 

Answers 4

Try that:

public static void Main(string[] args) {     // Add the event handler for handling UI thread exceptions to the event.     Application.ThreadException += new ThreadExceptionEventHandler(exception handler);      // Set the unhandled exception mode to force all Windows Forms errors to go through the handler.     Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);      // Add the event handler for handling non-UI thread exceptions to the event.      AppDomain.CurrentDomain.UnhandledException += // add the handler here      // Runs the application.     Application.Run(new ......); } 

Then you can log the message and the call stack and that should give you enough information to fix the issue.

Answers 5

I recommend you update your GUI to handle this situation automatically for your convenience. You instead use a set of inherited controls. You don't need to go into the designer, you can instead do a find/replace on the designer files only.

Here is the textbox and button ones. You would add more of them as needed and add other properties as needed. Rather than putting code on individual forms.

So, in this case, the TextBox class instead you would use TextBoxBackgroundThread and the Button class instead you would use ButtonBackgroundThread.

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows.Forms;  namespace ThreadSafeControls {     class TextBoxBackgroundThread : System.Windows.Forms.TextBox     {         public override string Text         {             get             {                 return base.Text;             }              set             {                 if (this.InvokeRequired)                     this.Invoke((MethodInvoker)delegate { base.Text = value; });                 else                     base.Text = value;             }         }          public override System.Drawing.Color ForeColor         {             get             {                 return base.ForeColor;             }              set             {                 if (this.InvokeRequired)                     this.Invoke((MethodInvoker)delegate { base.ForeColor = value; });                 else                     base.ForeColor = value;             }         }           public override System.Drawing.Color BackColor         {             get             {                 return base.BackColor;             }              set             {                 if (this.InvokeRequired)                     this.Invoke((MethodInvoker)delegate { base.BackColor = value; });                 else                     base.BackColor = value;             }         }     }      class ButtonBackgroundThread : System.Windows.Forms.Button     {         public override string Text         {             get             {                 return base.Text;             }              set             {                 if (this.InvokeRequired)                     this.Invoke((MethodInvoker)delegate { base.Text = value; });                 else                     base.Text = value;             }         }          public override System.Drawing.Color ForeColor         {             get             {                 return base.ForeColor;             }              set             {                 if (this.InvokeRequired)                     this.Invoke((MethodInvoker)delegate { base.ForeColor = value; });                 else                     base.ForeColor = value;             }         }           public override System.Drawing.Color BackColor         {             get             {                 return base.BackColor;             }              set             {                 if (this.InvokeRequired)                     this.Invoke((MethodInvoker)delegate { base.BackColor = value; });                 else                     base.BackColor = value;             }         }     } } 
Read More

Thursday, December 7, 2017

Using Custom Resource in DevExpress WPF Theme

Leave a Comment

Objective:

I have a WPF project which shall be themed using DevExpress Themes. There is a Login-UserControl that shall have a themable background image.

Implementation

I made a custom Theme. In that theme I have a Folder "CustomResources" in which there is an Image, let's call it "Background.png" and a "Brushes.xaml" that defines an ImageBrush like this:

<ResourceDictionary ...>     <ImageBrush x:Key="{CustomThemeKeyAssembly:CustomThemeResourcesThemeKey ResourceKey=LoginBackgroundImageBrush, ThemeName=CustomTheme}" ImageSource="Background.png" /> </ResourceDictionary> 

Accordingly, I have a shared Assembly CustomThemeKeyAssembly that derives a Custom ResourceThemeKey.

In the Project, I register and set the Theme using ApplicationThemeHelper

var theme = new Theme("CustomTheme") {     AssemblyName = "DevExpress.Xpf.Themes.CustomTheme.v17.2" }; Theme.RegisterTheme(theme);  ApplicationThemeHelper.ApplicationThemeName = "CustomTheme"; 

and I reference the Resource through

Background="{dxci:ThemeResource ThemeKey={CustomThemeKeyAssembly:CustomThemeResourcesThemeKey ResourceKey=LoginBackgroundImageBrush}}" 

As advised by DevExpress Knowledgebase / Support.

Problem

The Resource is only found and displayed, if I add a Merged Resource Dictionary like this:

ResourceDictionary loginBackgroundDictionary = new ResourceDictionary {     Source = new Uri($"pack://application:,,,/{MyProject.Properties.Settings.Default.ThemeAssembly};Component/CustomResources/Brushes.xaml", UriKind.Absolute) };  //Add LoginBackgroundImageBrush Dictionary Resources.MergedDictionaries.Add(loginBackgroundDictionary); 

No article or example mentions having to do this, though. So my impression is that I either am doing something wrong or I am missing some simple step like merging the Brushes.xaml into some ResourceDictionary.

Without that snippet I get a warning that the resource could not be found.

Question

Has anybody an idea where I am going wrong or what I am missing to get this working without that last snippet?

FYI: I am using DevExpress 17.2.3 and the ResourceKey Assembly is targeted to .net Framework 4.0

EDIT

Meanwhile, I tried adding the Brushes.xaml to Themes/Generic.xaml in the theme assembly like this:

<ResourceDictionary.MergedDictionaries>     <dxt:ResourceDictionaryEx Source="/DevExpress.Xpf.Themes.Office2016WhiteSE.v17.2;component/Themes/ControlStyles.xaml" />     <dxt:ResourceDictionaryEx Source="/DevExpress.Xpf.Themes.Office2016WhiteSE.v17.2;component/CustomResources/Brushes.xaml" /> </ResourceDictionary.MergedDictionaries> 

It didn't make any difference. Same behavior as before.

1 Answers

Answers 1

Problem solved!

The problem was in the CustomThemeKeyAssembly

The wrong implementation was

public class CustomThemeResourcesThemeKey : ThemeKeyExtensionBase {     public override Assembly Assembly => TypeInTargetAssembly != null ? TypeInTargetAssembly.Assembly : GetType().Assembly; } 

The working implementation is

public class CustomThemeResourcesThemeKey : ThemeKeyExtensionBase<ThemeResourcesThemeKeys> { } 

The breaking difference is the override of the Assembly property. The default implementation makes it work. I did that because it was done so in an example. Support told me to stick with the default implementation and it worked.

Read More

Wednesday, April 12, 2017

find the overlapping points of two series in devexpress chart

Leave a Comment

As you can see , I have a winform chart ( DevExpress Chart ) with two series . enter image description here
Each series has their points.
And what I want is to find the overlapping points of those two series ( pointed by green circle in picture ) .

For example ( 3.4 for the first overlapping point and 7.3 for the second overlapping point ) .
Thanks .

3 Answers

Answers 1

If you have direct access to the Series collection, you could:

var intersection = Series[0].Points.Intersect(Series[1].Points);

If you are creating discrete Point objects along the way, you may have to define the matching behavior, as well.

Answers 2

unfortunately, you will not find a direct answer, I faced the same problem once before and it ended up to find a workaround (which mostly will be a mathematic solution based on your business logic of the series).

Answers 3

As I understood each series in your case is just collection of sequantial points. All you need is to find intersection between line segments from each collection. I suggest to use for such purpose Bentley-Ottmann algorithm, which allows to find answer in (N log N) way (N is count of line segments from both series). Unfortunately I don't know whether there is implementation in c# (anytime you can use c++ implementation, if you will not find c# solution)

If you have always two series, defined by some two different functions, you can optimize algorithm in O (N) way (use "merge" logic)

Read More

Friday, April 29, 2016

How to make a aspxdocumentviewer direction rtl?

Leave a Comment

My goal is to have a right justified report. So I changed the text align from right to middle center justified because right justified alignment is not supported in document viewer I think. Now my text is justified but the direction is ltr. How to make it rtl?

I tried to set a rtl style for created html from document viewer by css and jquery but it ignores styles.

I am using devexpress report suit 15.2 with asp.net webforms.

How to do it?

1 Answers

Answers 1

Finally I fount it.

By adding the following code to the aspxdocumentviewer I was able to write css codes for it.

<dx:ASPxDocumentViewer ID="ASPxDocumentViewer1" runat="server">     <ToolbarItems>         <dx:ReportToolbarButton ItemKind="Search" />         <dx:ReportToolbarSeparator />     </ToolbarItems>     <SettingsReportViewer UseIFrame="false" /> </dx:ASPxDocumentViewer>  <style type="text/css">     body{         direction:rtl;     } </style> 
Read More

Friday, April 1, 2016

Pass a sql parameter value to devexpress report?

Leave a Comment

I want to pass a sql parameter from user form to my report class but it is not working and it does not create the report and when I open the report designer tab again after adding ID argument to the report class it refresh the report and delete my components.

What is the problem?

Here is my report class:

public SodoorZemanatName(long ID)     {         InitializeComponent(ID);     }      protected override void Dispose(bool disposing)     {         if (disposing && (components != null))         {             components.Dispose();         }         base.Dispose(disposing);     }      #region Designer generated code     private void InitializeComponent(long ID)     {             this.components = new System.ComponentModel.Container();             DevExpress.DataAccess.Sql.CustomSqlQuery customSqlQuery1 = new DevExpress.DataAccess.Sql.CustomSqlQuery();             DevExpress.DataAccess.Sql.QueryParameter queryParameter1 = new DevExpress.DataAccess.Sql.QueryParameter();             System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(SodoorZemanatName));             this.topMarginBand1 = new DevExpress.XtraReports.UI.TopMarginBand();             this.detailBand1 = new DevExpress.XtraReports.UI.DetailBand();             this.bottomMarginBand1 = new DevExpress.XtraReports.UI.BottomMarginBand();             this.sqlDataSource2 = new DevExpress.DataAccess.Sql.SqlDataSource(this.components);             ((System.ComponentModel.ISupportInitialize)(this)).BeginInit();             this.topMarginBand1.HeightF = 100F;             this.topMarginBand1.Name = "topMarginBand1";             this.detailBand1.HeightF = 100F;             this.detailBand1.Name = "detailBand1";             this.bottomMarginBand1.HeightF = 100F;             this.bottomMarginBand1.Name = "bottomMarginBand1";             this.sqlDataSource2.ConnectionName = "Context";             this.sqlDataSource2.Name = "sqlDataSource2";             customSqlQuery1.Name = "Query";             queryParameter1.Name = "ID";             queryParameter1.Type = typeof(long);             queryParameter1.ValueInfo = "0";             queryParameter1.Value = ID;             customSqlQuery1.Parameters.Add(queryParameter1);             customSqlQuery1.Sql = "select * from LG_Garanti where ID=@ID";             this.sqlDataSource2.Queries.AddRange(new DevExpress.DataAccess.Sql.SqlQuery[] {             customSqlQuery1});             this.sqlDataSource2.ResultSchemaSerializable = resources.GetString("sqlDataSource2.ResultSchemaSerializable");     this.Bands.AddRange(new DevExpress.XtraReports.UI.Band[] {     this.topMarginBand1,     this.detailBand1,     this.bottomMarginBand1});     this.ComponentStorage.AddRange(new System.ComponentModel.IComponent[] {     this.sqlDataSource2});     this.DataSource = this.sqlDataSource2;     this.Version = "15.2";     ((System.ComponentModel.ISupportInitialize)(this)).EndInit(); } #endregion 

And here is my calling:

SodoorZemanatName report = new SodoorZemanatName(1); ASPxDocumentViewer1.ReportTypeName = "SodoorZemanatName"; ASPxDocumentViewer1.Report = report; 

1 Answers

Answers 1

I guess you want to (1) click the button, (2) pass an ID then (3) open the report has content depends on that ID. So this is the way I did it, (I'm not sure is there any other way because devexpress is not open source):

  1. Design your dataset, it contains only the info you want to show using the dataset tool of Visual Studio. i.e. id, name, address, ..... Name that dataset = ReportDataset. That dataset has 1 table named MyTable.
  2. Design your report named MyReport using the GUI tool (remember to choose XtraReport), and select the datasource = that ReportDataset, do not edit the code generated by the GUI tool. Just use the GUI, click & click to add labels, add value from the ReportDataset.
  3. In you form, window form or whatever, the below should be inside the function triggered by button_click event (your "calling" in the last snippet of your question):

    DataSet new_ds = new DataSet(); ReportDataset.MyTable runtime_data = new ReportDataset.MyTable(); //get data from your database according to ID, Row by row  //then add them to the runtime_data table //in your case, add all the result of "select * from LG_Garanti where ID=@ID"; runtime_data.Rows.Add(new object[] {.blah blah..});// just add row, use whatever method you like new_ds.Tables.Add(runtime_data); //from this point, new_ds contains runtime data of the row(s) you want ID. //now just link that dynamic dataset to your designed report MyReport my_report = new MyReport(); my_report.DataSource = new_ds; // Show the print preview or do whatever you want  ReportPrintTool printTool = new ReportPrintTool(my_report); printTool.ShowRibbonPreviewDialog(); 

The above is to make it more flexible since the report can use its own dataset with the mixture of different tables,.... You can make it easier by reuse your own dataset in step 1. Hope this helps.

Read More