WPF Binding to TextBlock does not update target immediately - wpf

I don't think my question falls into any others here already so hopefully somebody can assist me.
I have a TextBlock binding set up using INotifyPropertyChnaged and it does work. The problem i am having is when it updates the target control (the TextBlock)
A quick run down of my code
namespace XYZ
{
public partial class Loading : Window
{
StatusMessage msg = StatusMessage.GetInstance();
public loading()
{
InitializeComponent();
this.DataContext = msg;
}
private void LoadBackEndUsers()
{
msg.Status = "Loading Users";
//txtStatus.GetBindingExpression(TextBlock.TextProperty).UpdateTarget();
//lblLoading.Focus();
this.txtStatus.DataContext = msg;
beUsers = new BackendUsers(Database);
allBEUsers = beUsers.GetAllUsers();
}
private void LoadProducts()
{
msg.Status = "Loading Products";
//txtStatus.GetBindingExpression(TextBlock.TextProperty).UpdateTarget();
//lblLoading.Focus();
this.txtStatus.DataContext = msg;
products = new Product(Database);
allProducts = products.GetAllProducts();
}
private void Window_ContentRendered(object sender, EventArgs e)
{
LoadBackEndUsers();
LoadProducts();
}
}
}
Now my issue is that my textblock displays "Loading Products" only after the method LoadProducts() is completed. It doesn't show "Loading Users" at all, so the target is only updating after everything has completed.
How do I get it to update immediately. The commented out bits was me just trying various things to try to force an update.
Any help would be greatly appreciated.
Kind regards,
Neill

The issue is that your retrieving of data is occurring on the same thread as your UI logic. This means that even though you change a property value and raise the OnPropertyChanged it is not re-evaluated until after your blocking data access is done. Instead you should use a BackgroundWorker. Here is a great article that walks through how you can use this:
http://elegantcode.com/2009/07/03/wpf-multithreading-using-the-backgroundworker-and-reporting-the-progress-to-the-ui/

Your StatusMessage class should implement INotifyPropertyChanged:
Edit : Im pretty sure that your Window_ContentRendered eventhanler blocks every UI update. I wrote a little sample that works for me:
public partial class MainWindow : Window
{
StatusMessage msg = new StatusMessage();
public MainWindow()
{
InitializeComponent();
this.DataContext = msg;
}
private void LoadBackEndUsers()
{
Task.Factory.StartNew(() =>
{
this.Dispatcher.BeginInvoke(new ThreadStart(() => msg.Status = "Loading Users"), DispatcherPriority.Normal);
//Load users here:
Thread.Sleep(2000);
this.Dispatcher.BeginInvoke(new ThreadStart(() => msg.Status = "Users loaded"), DispatcherPriority.Normal);
// If users loaded start load products:
LoadProducts();
});
}
private void LoadProducts()
{
Task.Factory.StartNew(() =>
{
this.Dispatcher.BeginInvoke(new ThreadStart(() => msg.Status = "Loading Products"), DispatcherPriority.Normal);
//Load products here:
Thread.Sleep(2000);
this.Dispatcher.BeginInvoke(new ThreadStart(() => msg.Status = "Products Loaded"), DispatcherPriority.Normal);
});
}
private void Window_ContentRendered(object sender, EventArgs e)
{
LoadBackEndUsers();
//LoadProducts();
}
}

Related

DataBinding Image.Source to a PhotoChooserTask result not working

I have the following class:
public class Sticky : INotifyPropertyChanged {
// ... some members
private BitmapImage _frontPic;
[DataMember]
public BitmapImage FrontPic {
get {
return _frontPic;
}
set {
_frontPic = value;
Changed("FrontPic");
Changed("FrontBrush");
}
}
}
I'm trying to databind it to this XAML:
<Image Width="173" Height="173" Source="{Binding FrontPic}" />
after launching a PhotoChooserTask with this code in my PhoneApplicationPage:
public Sticky Sticky { get; set; }
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e) {
Sticky = new Sticky();
DataContext = Sticky;
}
private void ShowFrontPicPicker(object sender, RoutedEventArgs e) {
var t = new PhotoChooserTask();
t.PixelHeight = 173;
t.PixelWidth = 173;
t.ShowCamera = true;
t.Completed += (s, ev) => {
if (ev.TaskResult == TaskResult.OK) {
var img = new BitmapImage();
img.SetSource(ev.ChosenPhoto);
Sticky.FrontPic = img;
}
};
t.Show();
}
However, my image remains blank. If I assign the Image.Source property directly to the Image without databinding, everything works. Databinding other properties works, it's just the image that seems to be the problem. How can I make the DataBinding on the image work?
Found the problem! The completed callback for PhotoChooserTask does not excecute in the UI thread, so a call to Dispatcher.BeginInvoke must be added:
t.Completed += (s, ev) => Dispatcher.BeginInvoke(() => {
// do stuff...
});

How to create and use WebBrowser in background thread?

How can I create System.Windows.Forms.WebBrowser in background STA thread? I try use some code like this:
var tr = new Thread(wbThread);
tr.SetApartmentState(ApartmentState.STA);
tr.Start();
private void wbThread()
{
CWebBrowser browser = new CWebBrowser();
var text = browser.Navigate("http://site.com", CWebBrowser.EventType.loadCompleted).Body.InnerHtml;
}
CWebBrowser - custom class, wich delegate System.Windows.Forms.WebBrowser object Navigate method and wait until page completed loads. The problem is LoadCompleted event on System.Windows.Forms.WebBrowser object never raises. I found some solution here, but it does not work (can't find method Application.Run() on my WPF app).
public class CWebBrowser : ContentControl
{
public readonly System.Windows.Forms.WebBrowser innerWebBrowser;
private readonly AutoResetEvent loadCompletedEvent;
private readonly AutoResetEvent navigatedEvent;
public enum EventType
{
navigated, loadCompleted
}
public CWebBrowser()
{
innerWebBrowser = new System.Windows.Forms.WebBrowser();
loadCompletedEvent = new AutoResetEvent(false);
navigatedEvent = new AutoResetEvent(false);
System.Windows.Forms.Integration.WindowsFormsHost host = new System.Windows.Forms.Integration.WindowsFormsHost();
host.Child = innerWebBrowser;
Content = host;
innerWebBrowser.DocumentCompleted +=new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(innerWebBrowser_DocumentCompleted);
innerWebBrowser.Navigated += new System.Windows.Forms.WebBrowserNavigatedEventHandler(innerWebBrowser_Navigated);
}
void innerWebBrowser_Navigated(object sender, System.Windows.Forms.WebBrowserNavigatedEventArgs e)
{
navigatedEvent.Set();
}
void innerWebBrowser_DocumentCompleted(object sender, System.Windows.Forms.WebBrowserDocumentCompletedEventArgs e)
{
if (((sender as System.Windows.Forms.WebBrowser).ReadyState != System.Windows.Forms.WebBrowserReadyState.Complete) || innerWebBrowser.IsBusy)
return;
var doc = innerWebBrowser.Document;
loadCompletedEvent.Set();
}
public System.Windows.Forms.HtmlDocument Navigate(string url, EventType etype)
{
if (etype == EventType.loadCompleted)
loadCompletedEvent.Reset();
else if (etype == EventType.navigated)
navigatedEvent.Reset();
innerWebBrowser.Navigate(url);
if (etype == EventType.loadCompleted)
loadCompletedEvent.WaitOne();
else if (etype == EventType.navigated)
navigatedEvent.WaitOne();
System.Windows.Forms.HtmlDocument doc = null;
Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Background, new Action(
delegate
{
doc = innerWebBrowser.Document;
}));
return doc;
}
}
Thansk for all advices and sorry for my bad english :o(
Why don't you use the default WebBrowser control like this?
public MainPage()
{
InitializeComponent();
System.Windows.Deployment.Current.Dispatcher.BeginInvoke(startNavigate);
}
void startNavigate()
{
WebBrowser wb = new WebBrowser();
wb.LoadCompleted += new LoadCompletedEventHandler(wb_LoadCompleted);
wb.Navigated += new EventHandler<System.Windows.Navigation.NavigationEventArgs>(wb_Navigated);
wb.Navigate(new Uri("http://www.google.com"));
}
void wb_Navigated(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
// e.Content
}
void wb_LoadCompleted(object sender, NavigationEventArgs e)
{
// e.Content when the document finished loading.
}
Edit: You are using old System.Windows.Forms.WebBrowser control, instead System.Windows.Controls.WebBrowser which is part of WPF.

Silverlight Datagrid Refresh

So I have a datagrid in Silverlight that is bound to a WCF that populates a list of class. I basically a pass a parameter to a Linq query. When I do a second query I get double the results, a third triple and so forth. What can I do to make it so when I send a call out to the service that I only get one set of results. I have attached my code in case it helps anyone.
private void button1_Click(object sender, RoutedEventArgs e)
{
dgOrder.ItemsSource = null;
Uri address = new Uri(Application.Current.Host.Source, "../Services/Service1.svc");
//var client = new Services.dataserviceClient("CustomBinding_dataservice", address.AbsoluteUri);
var client = new ServiceReference2.Service1Client("CustomBinding_Service1", address.AbsolutePath);
client.GetOrderCompleted += (s, ea) =>
{
dgOrder.AutoGenerateColumns = false;
//dgOrder.ColumnWidth.Value = 100;
dgOrder.Columns.Add(CreateTextColumn("SKU", "SKU"));
dgOrder.Columns.Add(CreateTextColumn("productname", "Product Name"));
dgOrder.Columns.Add(CreateTextColumn("itemnumber", "Item Number"));
dgOrder.Columns.Add(CreateTextColumn("cost", "Cost"));
dgOrder.Columns.Add(CreateTextColumn("asin", "ASIN"));
dgOrder.Columns.Add(CreateTextColumn("pendingorder", "Rank"));
dgOrder.Columns.Add(CreateTextColumn("rank", "Node"));
//dgOrder.Columns.Add(CreateTextColumn("w4", "AMZN"));
dgOrder.Columns.Add(CreateTextColumn("amazon", "AMZN"));
dgOrder.Columns.Add(CreateTextColumn("ourprice", "OurPrice"));
dgOrder.Columns.Add(CreateTextColumn("bbprice", "BuyBox"));
dgOrder.Columns.Add(CreateTextColumn("afner", "AFN"));
dgOrder.Columns.Add(CreateTextColumn("quantity", "INV"));
dgOrder.Columns.Add(CreateTextColumn("w4", "W4"));
dgOrder.Columns.Add(CreateTextColumn("w3", "W3"));
dgOrder.Columns.Add(CreateTextColumn("w2", "W2"));
dgOrder.Columns.Add(CreateTextColumn("w1", "W1"));
dgOrder.Columns.Add(CreateTextColumn("order", "Order"));
dgOrder.Columns.Add(CreateTextColumn("total", "Total"));
dgOrder.Columns.Add(CreateTextColumn("profit", "Profit"));
dgOrder.Columns.Add(CreateTextColumn("percent", "Percent"));
dgOrder.Columns.Add(CreateHyperlink("asin"));
dgOrder.ItemsSource = ea.Result;
Original = ea.Result;
};
client.GetOrderAsync(txtCompany.Text);
}
The problem is , you are creating a new(duplicate) event handler every time you press the Button. Due to having an extra event for each button pres you do, you get extra sets of data. You need to create your Event.Completed method outside the Button.Cliked event.
To clarify:
public partial class NewPage : Page
{
Uri address = new Uri(Application.Current.Host.Source, "../Services/Service1.svc");
ServiceReference2.Service1Client client = new ServiceReference2.Service1Client("CustomBinding_Service1", address.AbsolutePath);
public NewPage()
{
client.GetOrderCompleted += (s, ea) =>
{
//YOUR CODE
};
}
private void button1_Click(object sender, RoutedEventArgs e)
{
dgOrder.ItemsSource = null;
client.GetOrderAsync(txtCompany.Text);
}
}

WPF WebBrowser: How to set element click event?

I've figured out how to make everything red as soon as the page is finished loading:
private void webBrowser1_LoadCompleted(object sender, NavigationEventArgs e)
{
var doc = (IHTMLDocument2)webBrowser1.Document;
foreach (IHTMLElement elem in doc.all)
{
elem.style.backgroundColor = "#ff0000";
}
}
Now what if I want to make the element only change color when it's clicked? I see that elem has an onclick property, but it's type is dynamic so I don't know what to do with it. The documentation is pretty useless.
You could do it by using the HTMLDocumentClass instead of the IHTMLDocument2 interface:
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void webBrowser1_LoadCompleted(object sender, NavigationEventArgs e)
{
mshtml.HTMLDocumentClass doc = (mshtml.HTMLDocumentClass)webBrowser1.Document;
doc.HTMLDocumentEvents_Event_onclick += new mshtml.HTMLDocumentEvents_onclickEventHandler(OnClickHandler);
}
bool OnClickHandler()
{
mshtml.HTMLDocumentClass doc = (mshtml.HTMLDocumentClass)webBrowser1.Document;
mshtml.IHTMLWindow2 win = doc.parentWindow;
win.#event.srcElement.style.backgroundColor = "#ff0000";
return false;
}
}
The above solution, has one drawback: the onclick event does not bubble, even though false is returned (i.e. clicking at hyperlinks does not navigate to other pages).

Threading problem in WPF

I'm getting this Exception
System.InvalidOperationException was
unhandled by user code Message="The
calling thread cannot access this
object because a different thread owns
it."
whenever I run the following code
public partial class MainScreen : Window
{
Timer trm;
public MainScreen()
{
InitializeComponent();
trm = new Timer(1000);
trm.AutoReset = true;
trm.Start();
trm.Elapsed += new ElapsedEventHandler(trm_Elapsed);
}
void trm_Elapsed(object sender, ElapsedEventArgs e)
{
lblTime.Content = System.DateTime.Now;
}
}
guys any solution... I badly wann come out of it :(
Use DispatcherTimer instead:
public partial class MainScreen : Window{
DispatcherTimer tmr;
public MainScreen() {
InitializeComponent();
tmr = new DispatcherTimer();
tmr.Tick += new EventHandler(tmr_Tick);
tmr.Start();
}
void tmr_Tick(object sender, EventArgs e) {
lblTime.Content = System.DateTime.Now;
}
}
Any time you modify Windows controls you must do so on the UI thread (the one that created the controls).
See this question for lots of details.
To be short, you should use Dispatcher.Invoke method to update UI elements.

Resources