|
|
I have a ComboBox bound to a list of objects. When the user selects one, I want to warn the user. If they select No to my warning, I want to prevent the change.
The code in the VM's property setter runs, and it even appears to set SelectedVendor back to what it was, but in the UI it doesn't revert.
XAML
<ComboBox Grid.Row="0"
Grid.Column="1"
ItemsSource="{Binding JobVendors, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedVendor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource localComboStyle}"/>
ViewModel
private CompanyHeaderEntity _SelectedVendor;
public CompanyHeaderEntity SelectedVendor
{
get { return _SelectedVendor; }
set
{
var currentVendor = _SelectedVendor;
if (_SelectedVendor != value)
{
bool okToChange = true;
if (!_isLoading &&
PurchaseOrderHeader.PurchaseOrderItems != null &&
PurchaseOrderHeader.PurchaseOrderItems.Count > 0)
{
var message = "Changing vendors will remove all selected items. Continue?";
okToChange = MessageBox.Show(message, "Change Vendor", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes;
if (okToChange)
{
PurchaseOrderHeader.PurchaseOrderItems.Clear();
}
}
if (okToChange)
{
_SelectedVendor = value;
RaisePropertyChanged("SelectedVendor");
VendorSelected();
}
else
{
if (currentVendor != null)
{
_SelectedVendor = currentVendor;
}
}
}
}
}
If it's not broken, fix it until it is.
Everything makes sense in someone's mind.
Ya can't fix stupid.
modified 5-Apr-21 14:37pm.
|
|
|
|
|
In your last if statement, try adding this after setting the field to the current vendor:
RaisePropertyChanged("SelectedVendor");
".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
|
|
|
|
|
VS 2017
.Net 4.72
-------------------------------
I'm writing a WPF app that instantiates a class like this:
public class MyClass : INotifyPropertyChanged
{
private string currentFile;
private int fileCount ;
public string CurrentFile
{
get {return this.currentFile;}
set {if(value!=this.currentFile){this.currentFile=value;this.NtfyPropChange();}}
}
public int FileCount
{
get {return this.fileCount;}
set {if (value != this.fileCount){this.fileCount=value;this.NtfyPropChange();}}
}
public MyClass()
{
this.CurrentFile = string.Empty;
this.FileCount = 0;
}
public void DoSomethingWithFiles()
{
string[] files = Directory,.GetFiles(@"c:\mypath", *.*);
foreach(string file in files)
{
this.CurrentFile = file;
this.FileCount += 1;
}
}
}
In the XAML, I'm binding like this:
<TextBlock Text="{Binding x:Name="textblockCurrentFile" Path=myObject.CurrentFile}"/>
<TextBlock Text="{Binding x:Name="textblockFileCount" Path=myObject.FileCount}"/>
In my INotifyPropertyChanged -derived form, I have set the DataContext , instantiated MyObject , and when I inspect everything under the debugger, the two MyObject properties in question are being updated as expected as the method loop proceeds. In fact, if I hook the MyObject.PropertyChanged event in the form code, it is indeed called for every property change event.
Problem:
The form controls that are bound to these properties don't update when the property values change.
The MyObject class is created on the UI thread, and the method is not, itself, being executed in a different thread.
What am I dong wrong (or not doing at all)?
I tried doing this in the PropertyChanged event handler in the form, but it didn't fix my problem:
private void Tfs_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
Dispatcher.BeginInvoke(new Action(() =>
{
this.textblockCurrentFile.Text = this.myObject.CurrentFile;
this.textblockFileCount.Text = this.myObject.FileCount.ToString();
}));
}
".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
|
|
|
|
|
Are there any binding errors showing in the output window? If you use the live tree window, what do you see?
|
|
|
|
|
That was the first thing I looked for - no binding exceptions at all.
The form is actually a wizard page, and if I go to prev or next page and then come back, the text blocks show that last value to which they were set, so that tells me binding is okay.
New info - if an exception happens and the code shows a message box, the text blocks update.
It's almost like it's too busy to actually hit the UI, but like I said, this app isn't using threads.
".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
|
|
|
|
|
At a guess, do the bound values magically update when the loop finishes?
If you're calling DoSomethingWithFiles on the UI thread, you're blocking the UI thread until the method returns. No property updates will have any effect on the UI.
Either move the method to a background thread, or make it async and yield on every loop iteration:
public void DoSomethingWithFiles()
{
_ = DoSomethingWithFilesAsync();
}
private async Task DoSomethingWithFilesAsync()
{
string[] files = Directory.EnumerateFiles(@"c:\mypath", "*.*");
foreach (string file in files)
{
this.CurrentFile = file;
this.FileCount += 1;
await Task.Yield();
}
}
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
Well, that didn't work either. So I moved the code from the object into the form itself,
and it STILL doesn't update the controls. :/
It only updates the ui if i DON'T execute the code...
".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
modified 29-Mar-21 9:12am.
|
|
|
|
|
What's in the Window's code behind?
I'd (normally) expect to see:
public MyClass myObject {get;} = new MyClass();
...
(In the constructor; or preferably in the Loaded event - i.e. "after" all other initializations)
this.DataContext = this;
As for:
this.textblockCurrentFile.Text = this.myObject.CurrentFile;
You're referencing the "binding"; you should be referencing (the name of) the associated TextBlock, in this case.
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
|
|
|
|
|
In the constructor:
this.DataContext = this; also, the object is declared in the form as follows:
private MyObject myObj;
public MyObject MyObj { get { return this.myObj; } set { this.myObj = value; this.NotifyPropertyChange(); } } I already showed the xaml in the original post.
There are no binding exceptions being thrown when the code executes.
I have verified that the properties that are bound to the textblocks are indeed being updated (the setter in the properties is being called, and the values are in fact being updated), and that the form itself is recieving the PropertyChanged events (I hooked the PropertyChanged event to make sure).
As stated before, I am running this on the same thread as the UI, so cross-thread UI issues should not (and don't appear to) be a factor since the properties are getting updated and the form is being notified, and there are no exceptions being raised.
I even tried bringing the code into the form from the object, and the UI still doesn't update until the method that's running the code exits.
".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
|
|
|
|
|
Binding requires a public property:
Path=myObject.CurrentFile
...
private MyObject myObj;
public MyObject MyObj { get { return this.myObj; } set { this.myObj = value; this.NotifyPropertyChange(); } }
Don't see any matches.
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
|
|
|
|
|
Look again. The field is private the property is public.
".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
|
|
|
|
|
The "name" of your public property is "MyObj".
The "object" you're binding to is "myObject" (which is neither a property or field).
It does not compute.
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
|
|
|
|
|
I'm typing the code manually, because I can't just copy paste snippets due to security-imposed domain boundaries. I hate typing, so sometimes I might use shortcuts.
The long and short of it is that I've verified (through the debugger) that the bound properties are indeed being updated during the "long process", and the form is being notified of those changes as they occur, but the UI is not showing the changed values until the method either throws an exception or exits entirely.
".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
|
|
|
|
|
#realJSOP wrote:
this.DataContext = this; It could be a conflict with binding change notifications to INPC properties on a DependencyObject.
Have you tried using a DependencyProperty instead?
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
I've been using this.DataContext = this for years with no ill effects. How is it a bad thing?
".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
|
|
|
|
|
Yes, but what I'm saying is, I don't know if that works with INPC properties. Your DataContext is a DependencyObject, so I think the properties need to be DependencyProperties for binding to work properly.
"These people looked deep within my soul and assigned me a number based on the order in which I joined."
- Homer
|
|
|
|
|
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
|
|
|
|
|