I am using WCF Services
I have this problem:
When I retrieve data from server for my GridView at the start of an async function call, I set IsBusy = "True" . After the method is called, I set IsBusy = "False". During the method call RadBusyIndicator does not Display. I cannot understand what the problem is.
I have uploaded a simple project with this problem. Can you check it? Download
I moved the loading in a BackgroundWorker, can you try this:
private void LoadData()
{
//Activate BudyIndicator
App.Instance.SetBusy();
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
ObservableCollection<CustomerModel> LoadedCustomers = null;
//Create Client
proxy.ServicesClient client = new proxy.ServicesClient();
ObservableCollection<Customer> customers = client.GetCustomers();
LoadedCustomers = new ObservableCollection<CustomerModel>();
foreach (var item in customers)
{
LoadedCustomers.Add(new CustomerModel()
{
CustomerId = item.CustomerId,
Title = item.Title,
FirstName = item.FirstName,
MiddleName = item.MiddleName,
LastName = item.LastName,
CompanyName = item.CompanyName,
SalesPerson = item.SalesPerson,
EmailAddress = item.EmailAddress,
Phone = item.Phone
});
}
client.Close();
//Define return value
ea.Result = LoadedCustomers;
};
worker.RunWorkerCompleted += (o, ea) =>
{
//Get returned value
ObservableCollection<CustomerModel> model = ea.Result as ObservableCollection<CustomerModel>;
if (model != null)
{
Customers = model;
}
//Desactivate BusyIndicator
App.Instance.UnSetBusy();
};
worker.RunWorkerAsync();
}
Ok I see your problem. The Close method on your proxy wait the result of the asynch call.
Just move your client.Close(); in the GetCustomersCompleted method and this will work. (Tested with your sample)
private proxy.ServicesClient client = null;
private void LoadData()
{
App.Instance.SetBusy();
client = new proxy.ServicesClient();
client.GetCustomersCompleted += (s, e) =>
{
if (e.Error != null)
{
throw new Exception();
}
else
{
Customers = new ObservableCollection<CustomerModel>();
foreach (var item in e.Result)
{
Customers.Add(new CustomerModel()
{
CustomerId = item.CustomerId,
Title = item.Title,
FirstName = item.FirstName,
MiddleName = item.MiddleName,
LastName = item.LastName,
CompanyName = item.CompanyName,
SalesPerson = item.SalesPerson,
EmailAddress = item.EmailAddress,
Phone = item.Phone
});
}
OnPropertyChanged("Customers");
}
client.Close();//Close after the return
App.Instance.UnSetBusy();
};
client.GetCustomersAsync();
//client.Close();
}
}
If your window xaml is not inside the busy indicator it may not show. With that control you need to put the content you wish to be masked when the busy indicator is set to true inside the indicator tags. If you UserControl's primary display item is a Grid then wrap the grid in the busy indicator tags.
<UserControl>
<telerik:RadBusyIndicator IsBusy={Binding Busy}>
<Grid>
content...
</Grid>
</telerik:RadBusyIndicator>
</UserControl>
This should give you the result you are looking for.
If you are using a binding for the IsBusy property on the controller, which I assume you are, then you must implement the INotifyPropertyChanged interface so that when the value of the binding property is changed the UI is notified of that change and updates itself.
Your view model should have a property with a setter as follows:
public bool Busy
{
get{return _Busy;}
set
{
if(value != _Busy)
_Busy = value;
OnPropertyChanged("Busy");
}
}
This will notify the UI of the change;
If you are doing this already then I will need to see more of the relevant code to help more.
After I looked at your last post again if you are setting the IsBusy property to a string value that is your problem as that property takes a Boolean value.
I assume based on your code you wish to only put the busy indicator over the content control on the main window. My recommendation is to create a view model for the main window and use it as the datacontext for the page. I would also set up a property on the view model as explained above and set up a binding to that property. In the view model you can make the async call to the data store and on the return fill a collection property (recommend ObservableCollection) and bind your ListBox's IitemsSource property to that.
I hope this helpls
Related
I have a problem.I want to update the main UI in a user control.I tried so many times, but i didn't make it.The test is divided into two categories as follows:
Class 1:
I first assigned the main window control (tbInfo, TextBlock type) directly, unsuccessfully. So I created a textBlockUpdate class (implementing the property change notification interface) and bind its properties (TextMessage) to the Text property of tbInfo,unsuccessfully. Then I used the content control,also unsuccessfully.The code as follows:
//Feature code in user control.
info = string.Format("Adding file{0}", System.IO.Path.GetFileName(FileName));
if (_dataObject.MainWindow != null)
{
_dataObject.MainWindow.ShowInfo(info);
}
//Feature code in main window.
public void ShowInfo(string info)
{
if (Dispatcher.CheckAccess())
{
//tbInfo.Text = info;
// textBlockUpdate.TextMessage = info;
TextBlock textBlock = new TextBlock();
textBlock.Text = info;
tbInfoContainer.Content = textBlock;
}
else
{
Action<string> showInfoDel = (str) =>
{
// tbInfo.Text = info;
//textBlockUpdate.TextMessage = info;
TextBlock textBlock = new TextBlock();
textBlock.Text = info;
tbInfoContainer.Content = textBlock;
};
Dispatcher.BeginInvoke(showInfoDel, info);
}
}
Class 2:
I put the code in the user control into a thread, or did it not succeed.I tried three times, but I never succeeded.
1.
new Thread(()=>{
this.Dispatcher.Invoke(new Action(()=>{
//Add the feature code above here
}));
}).Start();
2.
Application.Current.Dispatcher.Invoke(new Action(() => {
//Add the feature code above here
}));
3.
Task task = new Task(()=> {
//Add the feature code above here
});
task.Start();
task.Wait();
So, can anyone tell me How to do to make it work?
This is not how it is done. Also setting the properties of a class is not called binding. It's a simple assignment.
A Binding connects two or more (MultiBinding) properties (target and source) and updates them automatically, when one of the two changes.
To allow the binding to detect property changes, you have to implement the participating properties either as DependencyProperty (mandatory for binding target - preferable on controls) or let them raise the INotifyPropertyChanged.PropertyChanged event on property changes.
Create the data and binding source
MainWindow.xaml
partial class MainWindow : Window
{
public static readonly DependencyProperty InfoProperty = DependencyProperty.Register(
"Info",
typeof(string),
typeof(MainWindow),
new PropertyMetadata(default(string)));
public string Info
{
get => (string) GetValue(MainWindow.InfoProperty);
set => SetValue(MainWindow.InfoProperty, value);
}
// Update the TextBlock via data binding
public void ShowInfo(string info)
{
this.Info = info;
}
}
Create the UI an set up the data binding
MainWindow.xaml
<Window>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=MainWindow},
Path=Info}" />
</Window>
See Microsoft Docs:
Data binding overview in WPF
How to: Implement a Dependency Property
I found a solution.Just change to the following method.
//Call the render thread update UI from the current thread
int i = 1;
while (i < 10)
{
if (_dataObject.MainWindow != null)
{
_dataObject.MainWindow.ShowInfo(info);
}
Dispatcher.Invoke(new Action(() =>
{
}), System.Windows.Threading.DispatcherPriority.Background);
// Thread.Sleep(100);
i++;
}
//this is a time-consuming process.
accessObj.AddFile(_dataObject.Path, fileInfo, fileContent);
//Call the render thread update UI from the current thread
int j = 1;
while (j < 10)
{
if (j == 1) {
_dataObject.AttachFiles.Add(fileInfo);
}
Dispatcher.Invoke(new Action(() =>
{
}), System.Windows.Threading.DispatcherPriority.Background);
//Thread.Sleep(100);
j++;
}
I have a problem with a ListView. I want each Cell to have a label and a switch but the text of the label does not appear.
Here is my code:
public class FilterPage : ContentPage
{
public FilterPage()
{
List<FilterCell> listContent = new List<FilterCell>();
foreach(string type in Database.RestaurantTypes)
{
FilterCell fc = new FilterCell();
fc.Text = type;
listContent.Add(fc);
}
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
types.ItemsSource = listContent;
var layout = new StackLayout();
layout.Children.Add(types);
Content = layout;
}
}
public class FilterCell : ViewCell
{
private Label label;
public Switch CellSwitch { get; private set; }
public String Text{ get { return label.Text; } set { label.Text = value; } }
public FilterCell()
{
label = new Label();
CellSwitch = new Switch();
var layout = new StackLayout
{
Padding = new Thickness(20, 0, 0, 0),
Orientation = StackOrientation.Horizontal,
HorizontalOptions = LayoutOptions.FillAndExpand,
Children = { label, CellSwitch }
};
View = layout;
}
}
If I enter a fixed Text in the FilterCell-Constructor it works fine (e.g.: label.Text = "Hello World")
When I create a Method for the ItemSelected-Event and read out the SelectedItem.Text Property I get the text I assigned as Value but it's never displayed. Only the switch is displayed when I try to run this Code.
Thanks for your help
Niko
Ohh boy. This code looks like a rape (sorry I had to say this).
Now let's see what's wrong:
The reason is you are mixing up data and view heavily.
The line
types.ItemTemplate = new DataTemplate(typeof(FilterCell));
means: "For each item in the list (ItemsSource) create a new filter cell". The FilterCells that you create in the loop are never displayed.
The easy fix
public class FilterPage : ContentPage
{
public FilterPage()
{
var restaurantTypes = new[] {"Pizza", "China", "German"}; // Database.RestaurantTypes
ListView types = new ListView();
types.ItemTemplate = new DataTemplate(() =>
{
var cell = new SwitchCell();
cell.SetBinding(SwitchCell.TextProperty, ".");
return cell;
});
types.ItemsSource = restaurantTypes;
Content = types;
}
}
There is a standard cell type that contains a label and a switch SwitchCell, use it.
As ItemsSource of your list, you have to use your data. In your case the list of restaurant types. I just mocked them with a static list.
The DataTemplate creates the SwitchCell and sets the Databinding for the Text property. This is the magic glue between View and data. The "." binds it to the data item itself. We use it, because our list contains items of strings and the Text should be exactly the string. (read about Databinding: https://developer.xamarin.com/guides/xamarin-forms/getting-started/introduction-to-xamarin-forms/#Data_Binding )
I striped away the StackLayout that contained the list. You can directly set the list as Content of the page.
Lesson
use standard controls, if possible
You should always try to remember to keep data and view apart from each other and use data binding to connect to each other.
Try to avoid unnecessary views.
In a Windows Phone 8.1 application (targeting Runtime not Silverlight), I have an ObservableCollection bound to a ListView, defined like this:
<ListView ItemsSource="{Binding ListItems, Mode=TwoWay}" CanReorderItems="True" ReorderMode="Enabled">
<ListView.ItemTemplate>
...etc...
In the ViewModel's constructor, I also have
ListItems.CollectionChanged += ListItems_CollectionChanged;
which is raising the event whenever any items are added and deleted - however, that's all handled from the VM not the View. Unfortunately, the event is not being raised when the items are reorder. I've set this up, moved some items, added a breakpoint in the event handler and added an item, and I can see the underlying ObservableCollection's order has changed as controlled from the View. So why isn't the event raising? And if it won't, what's the best practice for persisting a ListView's order in the database?
UPDATE:
The problem's actually bigger than I thought... it seems that the ListView.CollectionChanged event is not firing when adding an item either! It does when the application starts and loads them from the database but not when added by a user from the UI. This is very strange because the addition of items is performed using the exact same method. From the database:
private ViewModel MapFromModel(Item model, SQLiteAsyncConnection connection)
{
var viewModel = new ViewModel
{
Id = model.Id,
Text = model.Text,
Description = model.Description,
Added = model.Added,
Completed = model.Completed,
DueOn = model.DueOn,
ParentId = model.ParentId,
DisplayOrderNumber = model.DisplayOrderNumber,
IsNew = false
};
foreach (
var childViewModel in
connection.Table<Item>()
.Where(ci => ci.ParentId == viewModel.Id)
.ToListAsync()
.Result.Select(childItem => MapFromModel(childItem, connection)))
{
if (!_cache.Contains(childViewModel))
_cache.Add(childViewModel);
viewModel.AddItem(childViewModel);
}
return viewModel;
}
You see this recursive method calls the ViewModel's AddItem() method to add children (which are of the same type). I also have an ICommand bound to a button to add other items:
public void Execute(object parameter)
{
var viewModel = parameter as ViewModel;
if (viewModel == null) return;
AddItem(viewModel);
}
public static void AddItem(ViewModel viewModel)
{
// The DisplayOrderNumber of the new item needs to be the max of the current collection + 1.
var displayOrderNumber = viewModel.ListItems.Any()
? viewModel.ListItems.Max(ci => ci.DisplayOrderNumber) + 1
: 0;
var newText = string.Format("{0} {1}",
viewModel.Id == Guid.Empty ? "List" : "Item", displayOrderNumber + 1);
var newItem = new ViewModel
{
Text = newText,
NewText = newText,
ParentId = viewModel.Id,
InEditMode = true,
Added = DateTime.Now,
DisplayOrderNumber = displayOrderNumber,
IsNew = true
};
viewModel.AddItem(newItem);
viewModel.Save();
}
So why should the AddItem() method raise the event when called from the Service Layer but not from the ViewModel layer itself?
It turns out, as usual, the fault was entirely mine.
This was occurring because the event handling method ListItems_CollectionChanged was being disconnected. The cause of this was because of a Sort method that was replacing the underlying connection. I've got around this problem by adding the event handler, if needed, in the setter for the property (and of course removing any unused event handlers).
I'm using WPF to create an application to enable an organisation to enter different pieces of data into the application.I have a tab control to allow them to do this.
Then in a separate view, I have a series of different data grids showing the user what data they have inserted into the database. Containing buttons to either, add, update or delete the data they want.
Which leads me to my question. Currently, I am able to delete, and add data with ease and with no problem. But then comes my issue with trying to get the selected item to update, which it doesn't, resulting in a null reference exception.
If i set my property attributes programmatically though, it updates it fine. like so;public int _OrganisationTypeDetailID = 17; public int _OrganisationTypeID = 1;But I do not want this, as I want the ability for the user to select for themselves and update the data they need to.
Here's some of the code that may help in resolving my issue;
View Model;
public void UpdateOrganisationTypeDetail(OrganisationTypeDetail orgTypeDetail)
{
using (DBEntities context = new DBEntities())
{
var orgTD = context.OrganisationTypeDetails.Where(otd => otd.OrganisationTypeDetailID == SelectedType.OrganisationTypeDetailID).FirstOrDefault();
if (orgTD != null)
{
orgTD.Title = Title;
orgTD.FirstName = FirstName;
orgTD.Surname = Surname;
orgTD.Position = Position;
orgTD.DateOfBirth = DateOfBirth;
orgTD.Address = Address;
orgTD.Country = Country;
orgTD.Postcode = Postcode;
orgTD.PhoneNumber = PhoneNumber;
orgTD.MobileNumber = MobileNumber;
orgTD.FaxNumber = FaxNumber;
orgTD.Email = Email;
orgTD.NINumber = NINumber;
//context.OrganisationTypeDetails.Attach(orgTD);
context.OrganisationTypeDetails.ApplyCurrentValues(orgTD);
context.SaveChanges();
MessageBox.Show("Updated Organisation Type Details");
}
else
{
MessageBox.Show("Unable to update selected 'Type'.");
}
}
private OrganisationTypeDetail _SelectedType;
public OrganisationTypeDetail SelectedType
{
get
{
return _SelectedType;
}
set
{
if (_SelectedType == value)
return;
_SelectedType = value;
OnPropertyChanged("SelectedType");
}
}
public List<OrganisationTypeDetail> GetOrganisationTypeDetail //Loads data
{
get
{
using (DBEntities context = new DBEntities())
{
var query = from e in context.OrganisationTypeDetails
select e;
return query.ToList<OrganisationTypeDetail>();
}
}
}
private ICommand showUpdateCommand;
public ICommand ShowUpdateCommand //Update command
{
get
{
if (showUpdateCommand == null)
{
showUpdateCommand = new RelayCommand(this.UpdateFormExecute, this.UpdateFormCanExecute); //i => this.UpdateOrganisationTypeDetail()
}
return showUpdateCommand;
}
}
Code behind;
private void btnUpdateOrgTypeDetail_Click(object sender, RoutedEventArgs e)
{
OrganisationTypeDetail selected = dgOrgTypeDetail.SelectedItem as OrganisationTypeDetail;
OrganisationTypeDetailViewModel org = new OrganisationTypeDetailViewModel();
if (selected == null)
MessageBox.Show("You must select a 'Type' before updating.");
else
{
OrganisationTypeDetailUpdateView update = new OrganisationTypeDetailUpdateView();
update.ShowDialog();
org.UpdateOrganisationTypeDetail(selected);
Page_Loaded(null, null);
}
}
xaml;
<DataGrid Name="dgOrgTypeDetail" Height="145" Width="555"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding GetOrganisationTypeDetail}"
SelectedItem="{Binding SelectedType, Mode=TwoWay}">
Hope this issue can be resolved.
I would say that your best bet for this is to use commanding in the MVVM pattern to achieve this..
It looks like you're using a combination of MVVM and code behind and actually creating a new instance of the view model when your click event fires. Try binding the view model to your view once in the code behind of the view as the datacontext and then try updating the selected type..
Also when you're trying to do the update on SelectedType - look at your View using Snoop - see if the SelectedType property is still bound to the view.
ICommand UpdateOrgTypeDetail { get;}
Then in the view model constructor declare new instance
UpdateOrgTypeDetail = new DelegateCommand<object>(ExecuteUpdateOrgTypeDetail, CanExecuteUpdateOrgTypeDetail);
These two delegates will then allow you to click your button (which needs to bind to UpdateOrgTypeDetail)
<Button Command="{Binding UpdateOrgTypeDetail}" />
You should find that the update on the property is done correctly from here.
i wish to create a form at runtime that will read the columns for any datasource and create fields based on the columns and datatype just like a datagridviews insert line
Best regards,
Mark
What you are doing sounds a lot like how PropertyGrid already works, which is essentially:
foreach(PropertyDescriptor prop in TypeDescriptor.GetProperties(obj)) {
object val = prop.GetValue(obj);
string s = prop.Converter.ConvertToString(val);
Control cont = // TODO: create some control and set x/y
cont.Text = s;
this.Controls.Add(cont);
}
To avoid lots of work with alignment, using Dock to set the positions might help:
using(Form form = new Form())
using (PropertyGrid grid = new PropertyGrid())
{
form.Text = obj.ToString(); // why not...
grid.Dock = DockStyle.Fill;
form.Controls.Add(grid);
grid.SelectedObject = obj;
form.ShowDialog(this);
}
I wonder if it is easier to use PropertyGrid in simple circumstances, though. Or there are some 3rd-party versions that work similarly.
Ok so heres what i came up with!
public partial class Form2 : Form
{
private Boolean isBrowsable(PropertyInfo info)
{
return info.GetCustomAttributes(typeof(BrowsableAttribute), false).Length>-1;
}
public Form2()
{
InitializeComponent();
}
public Form2(Boolean showCheckBoxes)
{
InitializeComponent();
_showCheckBoxes = true;
}
private Boolean _showCheckBoxes;
private Object _reflection;
private TableLayoutPanel _table = new TableLayoutPanel{Dock=DockStyle.Fill, CellBorderStyle = TableLayoutPanelCellBorderStyle.Single};
public Object SelectedObject
{
get
{
return _reflection;
}
set
{
//clear all controls from the table
_table.Controls.Clear();
foreach (var property in _reflection.GetType().GetProperties())
{
if (isBrowsable(property))
{
if ((property.PropertyType == typeof(int)) || (property.PropertyType == typeof(string)))
{
var textField = new TextBox { Dock = DockStyle.Fill, AutoSize = true };
textField.DataBindings.Add("Text", _reflection, property.Name);
_table.Controls.Add(textField, 2, _table.RowCount += 1);
var propertyLabel = new Label
{
Text = property.Name,
Dock = DockStyle.Fill,
TextAlign = ContentAlignment.MiddleLeft
};
_table.Controls.Add(propertyLabel, 1, _table.RowCount);
if (_showCheckBoxes)
{
var checkBox = new CheckBox
{
AutoSize = true,
Name = property.Name,
Dock = DockStyle.Left,
CheckAlign = ContentAlignment.TopLeft
};
_table.Controls.Add(checkBox, 0, _table.RowCount);
}
}
}
}
//add one extra row to finish alignment
var panel = new Panel { AutoSize = true };
_table.Controls.Add(panel, 2, _table.RowCount += 1);
_table.Controls.Add(panel, 1, _table.RowCount);
if (_showCheckBoxes)
{
_table.Controls.Add(panel, 0, _table.RowCount);
}
Controls.Add(_table);
if (!Controls.Contains(_table))
Controls.Add(_table);
}
}
public Boolean Execute(Object reflection)
{
SelectedObject = reflection;
return ShowDialog() == DialogResult.OK;
}
}
thanks all!
I don't fully understand your question. Is it correct that you want to create a Windows form which provides input fields (textboxes, checkboxes, etc.) for all fields/properties of an object that you feed to the form as its DataSource?
You might have to use reflection for this (see the System.Reflection namespace). For example, to get a list of all properties:
using System.Reflection;
....
public object DataSource;
...
Debug.Assert( DataSource != null );
var properties = DataSource.GetType().GetProperties();
You would then instantiate one input control per property:
foreach ( var property in properties )
{
// extract some information about each property:
string propertyName = property.Name;
Type propertyType = property.PropertyType;
bool propertyReadOnly = !property.CanWrite;
// create input controls based on this information:
// ...
}
However, it might be fairly tricky to reliably map property types to the correct input control; for example, what are you going to do when you encounter a property with some unknown class as its type, or when a property is a collection of values? You might have to create a sub-form inside your form in some cases; in other cases, a listbox might be enough.
I've recently built a sample project that uses the Dynamic Data assemblies of ASP.NET to do just this for a WPF grid, but I'm sure you could adapt the concept to WinForms. Dynamic Data provides much richer metadata than just reflection or the database, but it does require an Entity Data Model, or a LINQ to SQL data model.
basically, all you need is a reference to System.Web.DymamicData, and maybe you can find something useful in my class:
public class DynamicDataGridBuilder<TContext, TEntity> where TEntity : EntityObject
{
readonly MetaModel model = new MetaModel();
public DynamicDataGridBuilder()
{
model.RegisterContext(typeof(TContext), new ContextConfiguration { ScaffoldAllTables = true });
}
public void BuildColumns(DataGrid targetGrid)
{
MetaTable metaTable = model.GetTable(typeof(TEntity));
// Decision whether to auto-generated columns still rests with the caller.
targetGrid.Columns.Clear();
foreach (var metaColumn in metaTable.Columns.Where(x => x.GetType().Name == "MetaColumn" && x.Scaffold))
{
switch (metaColumn.ColumnType.Name)
{
case "Boolean":
targetGrid.Columns.Add(new DataGridCheckBoxColumn { Binding = new Binding(metaColumn.Name), Header = metaColumn.DisplayName });
break;
default:
targetGrid.Columns.Add(new DynamicDataGridTextColumn { MetaColumn = metaColumn, Binding = new Binding(metaColumn.Name), Header = metaColumn.DisplayName });
break;
}
}
}
}
TContext is the type of your object model, and TEntity the type of the entity / class in that model your want to generate controls for.
Use control data binding. It will do all the work for you.