Xamarin.android GUI does not update after AddView - c#

I am creating an application for Xamarin.android in c# and ran into an odd problem.
I use a wrapper class which inflates a view. After that I add the view to a linearlayout with AddView. Sometimes the gui doesn’t update for no apparent reason. The Elements aren’t added. When I call the task button (I don’t know if that’s the right name – the button left of the home button) and then navigate back to my app the elements will appear. I read somewhere that you could use Invalidate() to update the gui but that does not seem to work.
Any suggestions are highly appreciated.
This is my constructor of the wrapper class:
public BoxDisplay(MainActivity activity, Product product, ViewGroup root)
{
View = activity.LayoutInflater.Inflate(Resource.Layout.BoxItem, root, false);
shelf = View.FindViewById<TextView>(Resource.Id.Shelf);
name = View.FindViewById<TextView>(Resource.Id.Name);
barcode = View.FindViewById<TextView>(Resource.Id.Barcode);
Stock = View.FindViewById<EditText>(Resource.Id.Stock);
typ = View.FindViewById<TextView>(Resource.Id.type);
this.activity = activity;
SetProduct(product);
Stock.ClearFocus();
Stock.Click += (o, arg) =>
{
Stock.SelectAll();
};
Stock.EditorAction += EditorAction;
}
and here I add the view long after the activity was created:
private void GenerateView(List<Product> products)
{
var boxlist = FindViewById<LinearLayout>(Resource.Id.BoxList);
foreach (var product in products)
{
var box = new BoxDisplay(this, product, boxlist);
boxDisplays.Add(box);
box.EnableEditText(false);
boxlist.AddView(box.View);
box.View.Click += (sender, e) => { box.UpdateBox(); };
}
}

A couple of ideas more than an answer, and maybe could be bad practice, but I do not run into your issue :
I usually add programmatically created Views in OnViewCreated (Fragment) / OnCreate(Activity)
Make sure your LinearLayout has a parent. It must be added to a parent view (root Layout, another Layout, a scrollview, ...).
Make sure you do not have a blocking, manually launched thread involving UI update (even through RunOnUiThread), which could be unlocked by activity recreation.
Make sure your view isn't "flattened" by another existing one in your layout (for example having a view matching parent size in both dimensions)
Beside the fact that it won't display your layout, is your application running properly (no permanent freeze) ?

Related

How do I clear a user control from a winform?

This is probably a basic question, but I can't find answers because the terms are generic.
I am building a WinForm aplication. Its purpose is to set up memory in a certain chip. I think the best way to organize the application is to have a user control for each chip type, derived from a generic parent class. Think of the children as "iphone," "android" and "blackberry," derived from a parent class "phone".
VS2017 Designer has a Panel where I want the control to be. On startup, I generate an object of the base class and add it to the panel. When I press a button, the old object is deleted and replaced with a new one. Each class has just one control, a label with distinctive text.
The problem is, after I press the button, I see both texts. The panel's Controls collection has just one element, but I see the text from both objects. I have tried Refresh, Update and Invalidate withe the same results.
What do I have to do to make the old text "go away" so the only thing I see is the latest object?
private ChipMemBase ChipMemControl = new ChipMemBase();
public Form1()
{
InitializeComponent();
//tbFeedback.Text = string.Format(fmtString, 0, 1, 2, 3, 4, 5);
cbChipName.SelectedIndex = 0;
tbVersion.Text = Version;
OriginalWindowColor = tbFeedback.BackColor;
ShowChipMemControl();
PrintToFeedback(Version);
}
private void ShowChipMemControl()
{
var ctl = pnlChipMem.GetChildAtPoint(new Point(5,5));
if (null != ctl)
{
if (ctl != ChipMemControl)
{
pnlChipMem.Controls.Remove(ctl);
ctl.Dispose();
pnlChipMem.Update();
Refresh();
}
}
if (null != ChipMemControl)
{
pnlChipMem.Controls.Add(ChipMemControl);
}
}
private void btnMakeChipMemory_Click(object sender, EventArgs e)
{
ChipMemControl = new ChipMemGen2();
ShowChipMemControl();
}
Screenshots before and after clicking Create
Your ShowChipMemControl gets the control at point 5,5 and checks if it's a ChipMemControl then removes it.
I'm guessing that the reason it's not getting removed is that the control at point 5,5 is not a ChipMemControl.
You can use:
pnlChipMem.Controls.Clear()
to remove all the controls
Or:
ChipMemControl cmc = pnlChipMem.Controls.OfType<ChipMemBase>().FirstOrDefault();
if (cmc != null)
{
pnlChipMem.Controls.Remove(cmc);
cmc.Dispose();
}
To only remove the first instance of ChipMemBase on your pnlChipMem panel.
Got it. The problem was from inheritance, not window behavior. Control lblDefault in the base class, carrying the inconvenient text, was still present in the child class. I had to make it Public in the base class and remove it in the child class constructor:
InitializeComponent();
Controls.Remove(lblDefault);
lblDefault.Dispose();
lblDefault = null;
The clue was this article and project:
dynamically-and-remove-a-user-control

Implementing an options dialog

in my application i want to implement an options dialog like you have in VisualStudios if you go to Tools->Options in the menubar. How can i do this? My first idea was to use pages and navigation but maybe there's an easier approach?
It's probably not the easiest way but I wrote this snippet that match your goal and it's a good exercise.
In an empty Windows Forms project add a ListBox (listBox1) and a Panel (panel1). Then create 2 UserControls (UserControl1 and UserControl2), these will be the content that is shown when you click the list.
In your Form1 class we create a ListItem class that will contain your menu options as such:
public partial class Form1 : Form
{
public class ListItem
{
public string Text { get; set; }
public UserControl Value { get; set; }
public ListItem(string text, UserControl value)
{
Text = text;
Value = value;
}
};
...
}
After that you add items to the ListBox right after InitializeComponent() in Form1:
public Form1()
{
InitializeComponent();
listBox1.DisplayMember = "Text";
listBox1.ValueMember = "Value";
listBox1.Items.Add(new ListItem("Item1", new UserControl1()));
listBox1.Items.Add(new ListItem("Item2", new UserControl2()));
}
This will make it so when you use listBox1.SelectedItem it will return an object that you can cast to a ListItem and access the associated UserControl.
To make use of this behaviour, go to designmode and double-click the ListBox, this'll add code for the SelectedIndexChanged event. We use this event to display the UserControl in the Panel panel1. This will clear any old Panel content and add a selected UserControl:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
panel1.Controls.Clear();
UserControl control = (listBox1.SelectedItem as ListItem).Value;
if(control != null)
{
panel1.Controls.Add(control);
control.Dock = DockStyle.Fill;
}
}
I suggest you try adding a button or something to differentiate the UserControls and play around. Have fun! :)
You should create a new Window and show that as opposed to create a page and navigate to it. Then you would call .show() on the new window for it to show.
Then you would change the look of the new window to however you want, the same as editing pages.
If you build your options into a full object model that matches the structure of the options window, then the best way is to use whatever navigation-aware UI binding that your MVVM toolkit uses. The options window would start off as a new root level window to which you would bind the root of your options data model.
So, in short think of the options dialog as a mini-application that uses the same structure as your main MVVM application, but with a different data model root.
If you plan to allow the user to cancel the changes to the options, then you would want your options data model to be clonable so that you can populate the options window with the clone and then swap out the real options with the new data if the user presses OK on the options window. If they select cancel you can just throw the cloned object away and destroy the window.

Multiple Pages withing one Form? C#

I haven't done this for a while so am not quite sure how to do what I need, but I am sure it is pretty simple.
Basically, I have a form with a navigation pane. I want to make it so when a user clicks a button on that pane, say 'Home' it changes the content on the form, but doesn't actually switch to another form, if you get me?
As in, I would like the navigation pane to stay as it is the entire time and I only want the content of the form to change. It is almost like the 'TabControl' tool in Visual Studio's 'Toolbox' although instead of the tabs being directly above the content, I want them to be buttons displayed in a side pane. See the image below for a better understanding. Thanks!
(Side pane, and header stays the same regardless on what button is pressed, but the content changes.)
I'd implement this using UserControls. One UserControl is shown when a button is clicked. I'd create an interface (for example IView) that would be implemented by each UserControl that declares common functionality, like for example a method to check whether you can switch from one to another (like a form's OnClosing event) like this:
public interface IView
{
bool CanClose();
}
public UserControl View1: IView
{
public bool CanClose()
{
...
}
}
public UserControl View2: IView
{
public bool CanClose()
{
...
}
}
Then, switching views is quite easy:
private bool CanCurrentViewClose()
{
if (groupBox1.Controls.Count == 0)
return true;
IView v = groupBox1.Controls[0] as IView;
return v.CanClose();
}
private void SwitchView(IView newView)
{
if (groupBox1.Controls.Count > 0)
{
UserControl oldView = groupBox1.Controls[0] as UserControl;
groupBox1.Controls.Remove(oldView);
oldView.Dispose();
}
groupBox1.Controls.Add(newView);
newView.Dock = Dock.Fill;
}
In a button you could do this:
private void btnHome_Click(object sender, EventArgs e)
{
if (CanCurrentViewClose())
{
ViewHome v = new ViewHome();
// Further initialization of v here
SwitchView(v);
}
else
{
MessageBox.Show("Current View can not close!");
}
}
I've successfully used this approach on many occasions.
Simplest way is to place multiple Panels as content holders, implement content manager which keeps references to Panels and with it show/hide desired panel.
Simple, but for smaller apps it will work
You can simply use a TabControl which has as many TabPages as you want. For the TabControl you can set the Alignment property to Left

Copying a TabItem with an MVVM structure

This is an attempt to expand on this question. In my WPF program I've been cloning tabItems by using an XamlWriter in a function called TrycloneElement. I originally found this function here, but the function can also be viewed in the link to my previous question.
Now that I am beginning to worry about functionality inside my program, I found that the TrycloneElement function does not replicate any code-behind functionality assigned to the tabItem that it is cloning.
Because of High Core's link and comment on my earlier question I decided to start implementing functionality on my tabItems through Data Binding with my ViewModel.
Here is a sample of a command that I've implemented:
public viewModel()
{
allowReversing = new Command(allowReversing_Operations);
}
public Command AllowReversing
{
get { return allowReversing; }
}
private Command allowReversing;
private void allowReversing_Operations()
{
//Query for Window1
var mainWindow = Application.Current.Windows
.Cast<Window1>()
.FirstOrDefault(window => window is Window1) as Window1;
if (mainWindow.checkBox1.IsChecked == true) //Checked
{
mainWindow.checkBox9.IsEnabled = true;
mainWindow.groupBox7.IsEnabled = true;
}
else //UnChecked
{
mainWindow.checkBox9.IsEnabled = false;
mainWindow.checkBox9.IsChecked = false;
mainWindow.groupBox7.IsEnabled = false;
}
}
*NOTE: I know that I cheated and interacted directly with my View in the above code, but I wasn't sure how else to run those commands. If it is a problem, or there is another way, please show me how I can run those same commands without interacting with the View like I did.
Now to the question:
After changing my code and adding the commands to my ViewModel, the TrycloneElement function no longer works. At run time during the tab clone I receive an XamlParseException on line, object x = XamlReader.Load(xmlReader); that reads:
I'm fine with ditching the function if there is a better way and I don't need it anymore. But ultimately, how do I take a tabItem's design and functionality and clone it? (Please keep in mind that I really am trying to correct my structure)
Thank you for your help.
Revision of Leo's answer
This is the current version of Leo's answer that I have compiling. (There were some syntax errors)
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues) })
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd
where dpd != null
select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly)
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
Here is my example of a properly-implemented dynamic TabControl in WPF.
The main idea is that each Tab Item is a separate widget that contains its own logic and data, which is handled by the ViewModel, while the UI does what the UI must do: show data, not contain data.
The bottom line is that all data and functionality is managed at the ViewModel / Model levels, and since the TabControl is bound to an ObservableCollection, you simply add another element to that Collection whenever you need to add a new Tab.
This removes the need for "cloning" the UI or do any other weird manipulations with it.
1.) To fix that XamlParseException, make sure you have a public constructor like an empty one, you probably defined a constructor and when you tried to serialize that object and deserialize it can't. You have to explicitly add the default constructor.
2.) I don't like the word clone, but I'd say, when they want to copy. I'll manually create a new tab item control then do reflection on it.
I have this code that I made
public static IList<DependencyProperty> GetAllProperties(DependencyObject obj)
{
return (from PropertyDescriptor pd in TypeDescriptor.GetProperties(obj, new Attribute[] {new PropertyFilterAttribute(PropertyFilterOptions.SetValues)})
select DependencyPropertyDescriptor.FromProperty(pd)
into dpd where dpd != null select dpd.DependencyProperty).ToList();
}
public static void CopyPropertiesFrom(this FrameworkElement controlToSet,
FrameworkElement controlToCopy)
{
foreach (var dependencyValue in GetAllProperties(controlToCopy)
.Where((item) => !item.ReadOnly))
.ToDictionary(dependencyProperty => dependencyProperty, controlToCopy.GetValue))
{
controlToSet.SetValue(dependencyValue.Key, dependencyValue.Value);
}
}
So it would be like
var newTabItem = new TabItem();
newTabItem.CopyPropertiesFrom(masterTab);

Silverlight 4 with RIA + EntityFramework + MVVM: Childwindow DomainContext Load does not refresh

I have a silverlight 4 application which uses RIA with EF to query multiple tables in a single DomainContext. BUGroup, BUGroupBuilding and vwBusinessUnit.
The UI basically loads the BUGroup entity set and I can select different BUGroups and it would load child tables like this:
I have a DomainContext that I'm passing to a childwindow in the Manage Buildings button like this:
ManageBuildingsChildWindow ManageBuildingscw = new ManageBuildingsChildWindow();
ManageBuildingscw.Closed += new EventHandler(ManageBuildingscw_Closed);
ManageBuildingscw.DataContext = null;
ManageBuildingsViewModel ManageBuildingsViewModel = new ManageBuildingsViewModel();
ManageBuildingscw.DataContext = ManageBuildingsViewModel;
and then I'm loading the childwindow context in the childwindow view model like this:
GetBUGroupResult = SecurityDomainContext.Current.Load(SecurityDomainContext.Current.GetBUGroupsCustomQuery(), LoadBehavior.RefreshCurrent, false);
GetBUGroupResult.Completed += new EventHandler(GetBUGroupResult_Completed);
here's the event handler for GetBUGroupResult
void GetBUGroupResult_Completed(object sender, EventArgs e)
{
GetBUGroupBuildings = SecurityDomainContext.Current.BUGroupBuildings.Where(w => w.BUGroupID == BUGroupID).ToList();
GetBUGroupResult.Completed -= new EventHandler(GetBUGroupResult_Completed);
}
I bind each BUGroupBuilding to a delete link in a datagrid and it deletes from database fine. when I click on the manage building button to invoke the child window and it loads fine for the first time. if i have 5 buildings, it loads 5 buildings. the problem is when i loads it 2nd or other times after deleting a few buildings. it retains the old DomainContext even after the load.
I even try setting the LoadBehavior to RefreshCurrent on the Load for the GetBUGroupsCustomQuery()
say I have 5 buildings in a group and i deleted 2 on parent window using the delete link so now i have 3. invokes the childwindow. it still shows 5.
Now I put a break on the DomainServices for GetBUGroupsCustomQuery() and i get the correct 3 value coming back
But during the GetBUGroupResult_Completed event handler, I'm seeing 5 buildings still. It looks like my DomainContext is not refreshing even when I specified the loadbehavior to refresh current.
any input?
I had a similar problem to this and it was solved with a workaround that loads data into the context and then detaches any objects in the entity collection that is not in the collection of the newly returned objects. Try something like this with your load operation:
SecurityDomainContext.Current.Load<YourObjectType>(
SecurityDomainContext.Current.GetBUGroupsCustomQuery(),
LoadBehavior.MergeIntoCurrent,
loadOperation =>
{
var results = context.Comments.Where(
entity => !loadOperation.Entities.Contains(entity)).ToList();
results.ForEach(entity => context.Comments.Detach(entity));
}, null);
I'm not sure if you'll need to replace <YourObjectType> with the entity type that is returned, or if you can just remove that part, but this should at least get you close.
You can also do:
var c = SecurityDomainContext.Current;
var group = c.BUGroups.Single(w => w.BUGroupID == BUGroupID);
c.Refresh(RefreshMode.StoreWins, group.BUGroupBuildings);
GetBUGroupBuildings = group.BUGroupBuildings.ToList();
By RefreshMode.StoreWins it is guaranteed that the current state of the database is retrieved.

Categories