|
So I did this (I'm gonna start using the actual object names because I'm too lazy to "obfuscate" them, and it makes it more consistent).
Notifiable is a class that implements INotifyPropertyChanged . I decided to inherit from this class because it already has the event mechanism built in (no point in reinventing the wheel).
public class Tfs : Notifiable
{
public string CurrentAction{ get; set; }
public string CurrentFile { get; set; }
public int FileCount { get; set; }
private int statusChanged;
public int StatusChanged { get { return this.statusChanged; } set { this.statusChanged = value; this.NotifyPropertyChanged(); } }
public void GetItemSet(int changeset, ref List<string> folders, ref List<FileBytes> files)
{
}
public void GetSpecificVersion(int changeset)
{
List<string> folders = new List<string>();
List<FileBytes> files = new List<FileBytes>();
this.CurrentAction = string.Concat("Retrieve item set - ");
this.FileCount = 0;
this.CurrentFile = this.AppFolder;
this.StatusChanged = 1;
this.GetItemSet(changeSet, ref folders, ref files);
this.CurrentAction = "Create folder - ";
foreach (string path in folders)
{
this.CurrentFile = path;
this.StatusChanged = 1;
this.CreateFolder(path);
}
this.CurrentAction = "Copy file - ";
foreach (FileBytes file in files)
{
this.FileCount += 1;
this.CurrentFile = file.FilePath;
this.StatusChanged = 1;
}
}
}
My form code-behind looks like this (only relevant code included). WizardPageBase is derived from UserControl and implements INotifyPropertyChanged .
public class WizPgSelectVersion : WizardPageBase
{
private string currentFile ;
private string currentAction;
private int fileCount ;
public string CurrentAction{get{return this.currentAction;} set{if(value!=this.currentAction){this.currentAction=value; this.NotifyPropertyChanged();}}}
public string CurrentFile {get{return this.currentFile ;} set{if(value!=this.currentFile ){this.currentFile =value; this.NotifyPropertyChanged();}}}
public int FileCount {get{return this.fileCount ;} set{if(value!=this.fileCount ){this.fileCount =value; this.NotifyPropertyChanged();}}}
public TFS Tfs { get; set; }
protected override void OnVisibleChanged()
{
if (this.Visibility == Visibility.Visible)
{
this.Tfs = (this.Tfs == null) ? TFSSingleton.Instance.Tfs : this.Tfs;
this.Tfs.PropertyChanged += this.Tfs_PropertyChanged;
}
else
{
if (this.Tfs != null)
{
this.Tfs.PropertyChanged -= this.Tfs_PropertyChanged;
}
}
}
int eventCounter = ;
private void Tfs_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
eventCounter++;
this.CurrentAction = this.Tfs.CurrentAction;
this.CurrentFile = this.Tfs.CurrentFile;
this.FileCount = this.Tfs.FileCount;
}
private void BtnGetFilesFromTfs_Click(object sender, RoutedEventArgs e)
{
try
{
foreach(VMSelectedApp app in appList)
{
this.Tfs.GetSpecificVersion2(app.ChangeSet);
}
}
catch (Exception ex)
{
}
}
}
And finally, the bound controls in the xaml (they're in a stackpanel, but that seemed irrelevant to me). I omitted colors and margin info.
<TextBlock x:Name="textblockCurrentAction" Text="{Binding Path=CurrentAction}" />
<TextBlock x:Name="textblockCurrentFile" Text="{Binding Path=CurrentFile}" />
<TextBlock x:Name="textblockFileCount" Text="{Binding Path=FileCount}" />
When I run the code:
0) There are no binding exceptions shown in the output window.
1) The Tfs PropertyChanged event handler is in fact detecting the property changed event from the Tfs object.
2) The UI is not updating.
3) At the end of the process, the eventCounter value is 3666, which is the expected number of times it should be called.
I don't think I forgot anything, but let me know if something doesn't look right.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
I tried setting the textblock.Text properties directly inside a Dispatcher.BeginInvoke (with DispatcherPriority.Background ), and it still doesn't update.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
Alright, I got it working mostly. I ended up unbinding the controls from the properties, and specifying a callback method that does a dispatcher.invoke to set the text locks directly.
What a royal pain in the ass.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
I have two WPF-applications. Let's call them A and B .
In A I wan't to start B as a new process and get the instance of B 's MainWindow to further gain access of the controls in B .
When A has started it sets up an instance of the Process class to start B . The instance is kept in A to help in any other way to access or terminate B .
In A I have a StartB method and a GetB method:
public partial class MainWindow : Window
{
Process Process { get; set; }
public MainWindow()
{
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
StartB();
GetB();
}
void StartB()
{
var path = @"C:\Some\Path\To\B.exe";
var info = new ProcessStartInfo(path);
Process = new Process();
Process.StartInfo = info;
Process.Start();
}
void GetB()
{
var title = Process.MainWindowTitle;
var visual = System.Windows.Interop.HwndSource.FromHwnd(Process.MainWindowHandle);
var window = visual.RootVisual as Window;
}
} The StartB method works. But the GetB method don't work.
This line in GetB returns null:
var visual = System.Windows.Interop.HwndSource.FromHwnd(Process.MainWindowHandle);
I have googled and I found somewhere that the call to System.Windows.Interop.HwndSource.FromHwnd should work, but it didn't.
Is there a solution for this?
Best regards,
Stefan
|
|
|
|
|
I suspect you're calling GetB too early - the main window hasn't shown up yet.
Assuming you have control of both applications, you'd probably be better off using IPC, probably using anonymous named pipes:
Pipe Operations in .NET | Microsoft Docs[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
I did wait so the MainWindow/Process of B had shown up before I ran the GetB() method.
But it didn't matter that I waited...
I don't want to alter the B WPF-application. Therefore I cannot implement IPC in any way in it.
|
|
|
|
|
An exe can be treated as a dll. You haven't said why you need to "start" B. If it's B's main window you want, you can instantiate it via A. That applies to anything public in B.
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
It might be time to tell the my idéa.
B is a WPF-application my company has. We don't have any unit test for the GUI of B. We want that.
So I thought if:
* B is a WPF-project in our Visual Studio solution.
* A is a unit test project in the same solution.
* A has some "code/package/NuGet" to start, terminate B and gain access of all public controls in B.
* In A we write the unit tests that use the "code/package/NuGet" to access the controls in B to do our unit tests.
For instance if I want to test a button's click event in B:
1) In A: Run a specific test case.
2) In A: Start B.
3) In A: Someway gain access of all public controls of B.
4) In A: Put a text in a TextBox located in B.
5) In A: Trigger the click event of a Button located in B.
6) In B: The Button's click event puts the TextBox's text in a TextBlock.
7) In A: Read the Text from the TextBlock inside of B.
8) In A: Verify the text and pass or fail the test.
9) In A: Terminate B.
I have looked at Appium with the WinAppDriver solution. That works, but it requires the WinAppDriver application running in the background to handle all communications between A and B. I don't want a third party or be forced to write any IPC in B.
Unit test should be easy to write and run. If each develop must install the WinAppDriver to run tests, it's just tedious.
However, I like the way that WinAppDriver use the AutomationProperties.AutomationId and other properties on controls to allow "A" to access it "B". I would like a similar solution.
@Gerry Schmitz
How should I do to instantiate B in A?
If that give me access to all public controls, that will be fine!
Best regards,
/Steffe
|
|
|
|
|
|
Since it's a "formal" test scenario, you add a "reference" to B's exe / namespace in A (versus dynamic assembly referencing); then it's:
var windowB = new B.MainWindow();
etc.
This assumes there isn't some code in B.App.Startup that MainWindow is dependent on (I use static "data" classes for lazy loading)
As for manipulating controls (and methods), you obviously need public accessors (in one form or another).
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
Why didn't I thought of that.
I tried it yesterday and it worked great. Thanks!
|
|
|
|
|
I'm building a WPF DataGrid. Nothing too hard there. However, I will have groups of columns that present Vendors. I want to put a group "header" above header section and have it scroll left & right with the grid's content.
See this image for my idea.
So the question is how to move the group header when the grid's data is scrolled?
Thanks!
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Maybe you could try something like the solution suggested here:
WPF multi-column super header[^]
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
I need to automatically generate a WPF DataGrid at runtime. Some of the columns will be known, and some will be added based off user selections. In all cases, I'll know the data types. The column definitions will all be rows in some table, allowing me to create any number of columns.
The problem that I'm going to run into is binding. Since there will be an unknown number of columns, I can't create an "concrete" entity to bind to. So, the real question here is how to represent the data when I don't know all the columns.
I came across some examples of ExpandoObject usage and put together this sample:
static void Main(string[] args)
{
var x = new ExpandoObject() as IDictionary;
x.Add("NewProp", "Hello World!");
IDictionary propertyValues = x;
foreach (var property in propertyValues.Keys)
{
Console.WriteLine(String.Format("{0} : {1}", property, propertyValues[property]));
}
dynamic expando = new ExpandoObject();
expando.SomeStringProp = "C";
expando.SomeNumberProp = 12;
expando.MyMethod = new Func(() =>
{
return 55;
});
Console.WriteLine(expando.SomeStringProp);
Console.WriteLine(expando.SomeNumberProp);
Console.WriteLine(expando.MyMethod());
Console.ReadLine();
}
I'm concerned about performance here. If anyone has a better way I'd like to hear about it.
Thanks!
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 19-Mar-21 16:58pm.
|
|
|
|
|
How about loading the data into a DataTable and binding the grid to the table's .DefaultView property?
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Ya, a table would be easier to build. I'll look into it.
Thanks!
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Ok, I'm stuck here. I can see rows - no data just the highlight - and no columns.
What am i doing wrong??
XAML
<DataGrid Grid.Row="1"
AutoGenerateColumns="False"
ItemsSource="{Binding BidBudgetData.DefaultView}"/>
ViewModel
private DataTable _BidBudgetData;
public DataTable BidBudgetData
{
get { return _BidBudgetData; }
set
{
if (_BidBudgetData != value)
{
_BidBudgetData = value;
RaisePropertyChanged("BidBudgetData");
}
}
}
private void CreateBidBudgetTable()
{
var bidBudgetData = new DataTable();
bidBudgetData.Columns.Add("Plan Type", typeof(string));
bidBudgetData.Columns.Add("Living Sq Ft", typeof(decimal));
foreach (var planType in ProjectPlanTypeSummaries.OrderBy(x =>x.Plan).ThenBy(x => x.Elevation))
{
DataRow row = bidBudgetData.NewRow();
row["Plan Type"] = $"{planType.Plan}{planType.Elevation}";
row["Living Sq Ft"] = planType.LivingSqFt;
bidBudgetData.Rows.Add(row);
}
BidBudgetData = bidBudgetData;
RaisePropertyChanged("BidBudgetData");
}
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
You've set AutoGenerateColumns to false , but you haven't manually defined any columns in the DataGrid .
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Yup. I'm an idiot. I spent hours trying to figure out why I'm not seeing anyting.
Thanks!
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
|
|
|
|
|
Using VS2019, .Net framework 4.72:
I wrote a simple app that has a collection class (derived from ObservableCollection ) of items (that inherit from INotifyPropertyChanged ). I'm specifying 5 items to process.
Each of these items has a DoWork method that simply sits and spins for a randomly selected amount of time (from 1 to 30 seconds). While an item is "spinning", it updates a Progress property, as well as a Status property. The updates to these properties are intended to update the UI.
The observable collection class contains a method that does the following:
public void Start()
{
try
{
var list = this.Where(x=>x.Status == WorkerStatus.NotStarted);
Parallel.ForEach(list, this.options, worker=>
{
worker.DoWork(options.CancellationToken);
});
}
catch (OperationCanceledException ex)
{
if (ex != null) { }
}
}
I'm not getting any exceptions, but the UI updates appears to reflect item status updates all at the same time.
In an attempt to fix the update issue, I call this method from the Progress and Status property set method
private void DispatcherNotify(params string[] propertyNames)
{
Dispatcher.CurrentDispatcher.Invoke(()=>
{
foreach(string name in propertyNames)
{
this.NotifyPropertyChanged(name);
Debug.WriteLine("{0} - {1}", this.IDName, name);
}
});
}
I have verified that this method is indeed being called as expected, but the UI isn't updating (each "item" has its own UI component that is supposed to be updated).
The UI status update is handled by a converter, and that converter is called for all five items , but only when all 5 items have finished processing.
What am I doing wrong?
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
I would've created an ObservableCollection of "user controls" (that implement INotify).
I then invoke PropertyChanged on the user controls; which could be hosted in any type of "panel" type control.
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
The items are bound to a user control.
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
But you appear to be scoping the property changed to the collection, not the items.
It was only in wine that he laid down no limit for himself, but he did not allow himself to be confused by it.
― Confucian Analects: Rules of Confucius about his food
|
|
|
|
|
The user control element that is supposed to show the update is bound to the item (which is the data context). The UI does in fact update, but not while any of the 5 items is processing
".45 ACP - because shooting twice is just silly" - JSOP, 2010 ----- You can never have too much ammo - unless you're swimming, or on fire. - JSOP, 2010 ----- When you pry the gun from my cold dead hands, be careful - the barrel will be very hot. - JSOP, 2013
|
|
|
|
|
I want to collapse or expand all TreeView nodes under parent node if user holds down Left Control key and presses left mouse button on expansion arrow of tree view.
How do you do this in WPF? It's not as obvious as it was in WinForms.
|
|
|
|