WPF - fill with data control, that is inside of another control - wpf

i have written some code, which fills controls with some data from a database. Everything works fine if controls are put directly in a window. But how to fill controls, which are inside other controls, such as TabControls, GroupBoxes etc.
My code looks like this:
Some window:
private void LoadDataP()
{
if (ID.Length > 0)
{
if (baseButtons.LoadProcedureSelectName != string.Empty)
LoadData = SqlHelper.GetTable(baseButtons.LoadProcedureSelectName, new string[] { IdName, ID });
if (LoadData != null)
foreach (DataRow dr in LoadData.Rows)
{
SqlHelper.FillWindowControllsWithData(myGrid, dr);
}
}
}
There are methods in another class.They do the main job:
public static void FillWindowControllsWithData(Grid windowGrid, DataRow dataRow)
{
foreach (Control ctrl in windowGrid.Children)
{
FillWindowControllsWithData(ctrl, dataRow);
}
}
public static void FillWindowControllsWithData(Control ctrl, DataRow dataRow)
{
if (ctrl.Name.IndexOf("db_") == 0)
{
if (ctrl is TextBox)
{
if (dataRow.Table.Columns.Contains(ctrl.Name.Substring(3)))
{
((TextBox)ctrl).Text = dataRow[ctrl.Name.Substring(3)].ToString();
}
}
} //end if
}
So does anybody know how to fill data in a texbox, which is in some groupbox or tabcontrol, which also have children..?

Just to amplify on what Dabbleml says: Writing WPF applications using Windows Forms techniques is a road to despair and misery. The happy, joyous, and free way to build WPF applications is through the MVVM pattern and data-binding.
The buzzwords make this sound a lot harder than it actually is. In practice it's conceptually quite simple:
Design your WPF window. This is your view.
Build a class that has exposes each piece of data that your window will display as a property. This is your view model. Add a method to the class that populates it from the data source. (This, by the way, is your model. Now you can say that you're using the MVVM pattern and impress girls at parties.)
Create a data context in your WPF window that will hold an instance of this class, and bind the controls to the class's properties.
What happens when you do this: the actual code you write all involves manipulating data, not the UI. When someone comes to you and asks if your window can display some kind of dancing bologna when some weird condition occurs, you don't write any UI code; you just add a property to your view model, and bind the dancing-bologna control in your view to it.

I think you could write the FillWindowControllsWithData method recursively. You need to alter the signature of the method though:
public void FillWindowControllsWithData(FrameworkElement element, DataRow dataRow)
{
if (element is TextBox)
{
if (element.Name.StartsWith("db_")
{
if (dataRow.Table.Columns.Contains(element.Name.Substring(3)))
{
((TextBox)element).Text = dataRow[element.Name.Substring(3)].ToString();
}
}
}
else if(element is Panel)
{
foreach(FrameworkElement el in ((Panel)element).Children)
{
FillWindowControllsWithData(el, dataRow);
}
}
else if(element is ContentControl)
{
FillWindowControllsWithData(((ContentControl)element).Content, dataRow);
}
//else do nothing
}
However,this definitely is not the way to do it. You need to fill your controls through databinding. Start here to make a start. Good Luck!

Related

What's a Good Way to Obtain ListView Item Containers?

My question is for UWP but the solution might be the same in WPF so I tagged that as well.
I'm trying to implement this extension method in a custom GridView and ListView so that when a selection changes, the selected item will always smoothly animate into view.
The extension method works great. However obtaining the UIElement container to send it does not work so great.
ListView.Items is bound to a collection in a ViewModel. So ListView.Items are not UIElements, but rather data objects. I need the SelectedItem's corresponding UIElement container.
First I tried this:
void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_scrollViewer != null && this.ItemsPanelRoot != null && this.Items.Count > 0)
{
var selectedListViewItem = this.ItemsPanelRoot.Children[this.SelectedIndex];
if (selectedListViewItem != null)
{
_scrollViewer.ScrollToElement(selectedListViewItem);
}
}
}
This works at first but is actually no good. The indices of 'ListView.ItemsPanelRoot.Children' eventually start to diverge from 'ListView.Items' as the layout is updated dynamically.
Then I tried this, which so far seems to work fine:
void ListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_scrollViewer != null && this.Items.Count > 0)
{
var selectedListViewItem = this.ItemsPanelRoot.FindDescendants<ListViewItem>()
.Where(x => x.Content == this.SelectedItem).FirstOrDefault();
if (selectedListViewItem != null)
{
_scrollViewer.ScrollToElement(selectedListViewItem);
}
else
{
throw new Exception();
}
}
}
The problem is that it seems incredibly expensive to do that query each time and also unsafe because there's no assurance that the container is available yet. I feel (hope) I'm missing something and that there's a proper way to do this.
Note: 'FindDescendants' is an extension method from Windows UI Toolkit, does the same thing as VisualTreeHelper.
ItemsControls have an Item​Container​Generator property, which holds an Item​Container​Generator object that you can use to get item containers for items and item indexes and the like.
E.g.
var container = listView.ItemContainerGenerator.ContainerFromItem(item);
In UWP, the ContainerFromItem and similar methods are also directly available in the ItemsControl class, so that you could write
var container = listView.ContainerFromItem(item);

DataAnnotations using ViewModels and ErrorProvider for WinForms

I am trying to apply some validation to a rather complex set of technologies for our existing product while implementing a new architecture. Most of this is going well, however I still need to use WinForms for the existing product and therefore need to use the ErrorProvider class for showing validation errors. (The new product will be able to use the new architecture via WPF/MVC too, but I don't have the time or resources for a complete rewrite from scratch to eliminate the WinForms at the moment, hence the mix of technologies)
The basic layout of the new architecture is as follows:
Database -> Model (via NHibernate) -> ViewModel -> Form (using BindingSource)
So my question is, how can I utilise a ValidationResult from a failed DataAnnotation check on a Model's property on a ErrorProvider on the Form?
I have managed to get the list of ValidationResults up to the Form, but setting them to a specific control is alluding me without writing code for each Control, I would prefer to create a generic way of doing this via the BindingSource, possibly in a base Form.
I know I could do this a lot easier if the DataAnnotations were on the ViewModels, but if I did that then I would have to keep all of them up to date if a change was made to the Model/Database table, and that would require plenty of repeated code.
I understand that this question is a bit vague, but considering the fact this spans the majority of the architecture, I can't see a better way of explaining it without writing reams of mostly irrelevant code. If you want extra information, then please ask and I will provide it.
Thanks very much.
not sure if this will help, but see if changing your btn_Save to be like this and then add the GetControlBoundToMember method as well, I am guessing your btnSave method looks similar to the one below. You will also need to add an ErrorProvider Control to your form and call it err1 and move any controls that might be in a groupbox out of the groupbo and place them onto the form, unless you create a recursive method that search through controls that have a collect of controls.
private void btnSave_Click(object sender, EventArgs e)
{
if (_entity != null)
{
try
{
_service.Save(_entity.UpdateEntity());
}
catch (InvalidOperationException ex)
{
//do something here to display errors
listBox1.Items.Clear();
foreach (var r in _entity.Errors)
{
listBox1.Items.Add(r.ErrorMessage);
foreach (var c in GetControlBoundToMember(r.MemberNames.ToList()))
{
err1.SetError(c, r.ErrorMessage);
}
}
}
}
}
private IList<Control> GetControlBoundToMember(IList<string> memberNames)
{
List<Control> controls = new List<Control>();
foreach (Control control in this.Controls)
{
foreach (var mn in memberNames)
{
foreach (Binding binding in control.DataBindings)
{
if (binding.BindingMemberInfo.BindingField == mn) controls.Add(control);
}
}
}
return controls;
}
AB

usercontrol adding another usercontrol programmatically in design time

I have a usercontrol (UC1) that changes aspect at design time according to what the user wants to show.
A regular button that pops a window with usercontrol UC2 (the window is only shown at runtime)
The UC2 directly hosted in UC1 (the regular button is then not shown)
Since I want to use the same UC2 instance in both situation, I just transfer ownership between UC1 and the form.
public UC1 ()
{
_uc2 = new UC2 ();
}
public bool DisplayModeSimple
{
get { return _displayModeSimple; }
set
{
_displayModeSimple = value;
if (_displayModeSimple)
{
// ... Verify if _uc2 is already in Controls...
Controls.Remove (_uc2);
uiButton.Visible = true;
}
else
{
// ... Verify that _uc2 is not in Controls ...
Controls.Add (_uc2);
uiButton.Visible = false;
}
}
}
private void HandleButtonClick (object sender, EventArgs e)
{
// Not called if DisplayModeSimple=false since button is hidden...
using (var form = new PopupForm (_uc2))
{
form.ShowDialog (this);
}
}
Works fine in both design and runtime mode.
In design mode if I change the display mode UC1 behaves correctly.
However, controls that are on UC2 can be clicked like if it was runtime.
If I then close the form hosting UC1 and reopen it everything is back to normal, i.e., I cannot "click" on any controls in UC2.
The problem is that your first UserControl is hosted on VS, so it knows to be in design mode. The second UserControl is hosted in the first UserControl, so as its host is not a Designer, it thinks to be in a normal container and behaves accordingly. How to solve that is a bit tricky, as there isn't asimple solution AFAIK. Here you can find some workarounds. Another could be to test Site.DesignMode recursively, but it depends on the level of depth of your controls.

Technical code into the ViewModel in wpf?

I have a UserControl with some custom dependency properties bound to a clr property in the ViewModel. The ViewModel has application logic where I deal with the TextPointer/TextRange classes with a FlowDocument.
Should I put that stuff into the code-behind of the UserControl or in the ViewModel?
ranges.Clear();
TextRange range = new TextRange(boundXamlDocument.ContentStart, boundXamlDocument.ContentEnd);
foreach (var block in boundXamlDocument.Blocks)
{
if (block is Paragraph)
{
Paragraph p = block as Paragraph;
//if paragraph has Strikethrough, then do not loop its inlines.
if (p.TextDecorations.Contains(TextDecorations.Strikethrough[0]))
{
TextRange tr = new TextRange(p.ContentStart, p.ContentEnd);
ranges.Add(tr);
}
else
{
foreach (var run in p.Inlines)
{
if (run.TextDecorations.Contains(TextDecorations.Strikethrough[0]))
{
TextRange tr = new TextRange(run.ContentStart, run.ContentEnd);
ranges.Add(tr);
}
}
}
}
}
I only bother to design custom/user controls when I have a concept that won't fit well into any of the usual controls (rare to never), or I have custom control behavior that I want to reuse (more common).
The more abstract your control can be, the more reusable it will be. Although, making it so abstract that no one would get any benefit from it would be doing too much :)
If you have application logic, it is best to define it in the view model (or model) when at all possible. When that logic changes, it won't break other users of your control.
If a feature of the control isn't specific to the exact presentation/user input style, and is specific to that instance of the control, you should probably put it in the view model.
Edit:
From your comments, it seems that the code you are trying to write depends on UI elements (TextBlock text decorators). This means it must and should go in the view.

WinForms TabControl validation: Switch to a tab where validation failed

I currently have a Form with a TabControl containing some TabPages. Each TabPage has several controls with validation logic and appropriate ErrorProviders. On my OK_Button_Clicked event, I call Form.ValidateChildren() in order to determine whether to save and close the form . Now, suppose I have a control in tab 1 that fails validation, but the currently visible tab is tab 2. When the user presses OK, he would get no visual indication as to why the form is not closing. I would like to be able to automatically switch to a tab where validation failed, so the user would see the ErrorProvider's indication of error.
One approach would be subscribing to the Validated and validating events of all appropriate controls, and knowing which tab each of them is in, whenever one fails validation, a list of tabs that failed validation could be built. Since no ValidationFailed event is generated as far as I know, this could be cumbersome (e.g. defining a boolean for each control, setting it to false before validation and to true on its Validated event). And even if I had such an event, I would be forced to listen to many validation events, one for each control that might fail validation, and maintain the list of unvalidated tabs in code. I should note here that subscribing directly to the TabPage's validation events doesn't work, because they pass as validated even if controls contained inside them fail validation.
Another approach could leverage the fact that the controls in my TabPage happen to be custom controls. I could then make them implement an interface such as:
interface ILastValidationInfoProvider
{
public bool LastValidationSuccessful {get; set;}
}
For example:
public MyControl : UserControl, ILastValidationInfoProvider
{
MyControl_Validing(object sender, object sender, CancelEventArgs e)
{
if (this.PassesValidation())
this.ErrorProvider.SetError(sender, null);
LastValidationSuccessful = true;
else
e.Cancel = true;
this.ErrorProvider.SetError("Validation failed!", null);
LastValidationSuccessful = false;
}
}
And then, after the call to ValidateChildren I could use code such as:
public void OK_Button_Click
{
if (form.ValidateChildren())
this.Close()
else
foreach (TabPage tab in this.TabControl)
foreach (Control control in tab.Controls)
{
ValidationInfo = control as ILastValidationInfoProvider
if (ValidationInfo != null && !ValidationInfo.LastValidationSuccessful)
{
this.TabControl.SelectTab(tab);
return;
}
}
}
I like this approach better but it doesn't cater to cases where the controls being validated are not custom.
I would gladly use a better approach. Any ideas?
EDIT I am using Form.AutoValidate = EnableAllowFocusChange (as recommended by Chris Sells in his WinForms book), So the focus can indeed change from controls that failed validation (including moving to other tabs). I have also updated the sample code for the custom control to emphasize the fact that the ErrorProvider resides internally inside it.
OK so I finally figured it out.
I keep a dictionary whose keys are the TabPages and the values are HashSets of unvalidated controls within the corresponding tab. This is easily done by subscribing to all the validating and validated events of the controls in each tab. Finally, in OK_BUtton_Click, if ValidateChildren fails, I know one of the hashsets will be none empty and I simply jump to the first unvalidated tab (only if the currently selected tab doesn't have any error itself).
Dictionary<TabPage, HashSet<Control>> _tabControls
= new Dictionary<TabPage, HashSet<Control>>();
public OptionsForm()
{
InitializeComponent();
RegisterToValidationEvents();
}
private void RegisterToValidationEvents()
{
foreach (TabPage tab in this.OptionTabs.TabPages)
{
var tabControlList = new HashSet<Control>();
_tabControls[tab] = tabControlList;
foreach (Control control in tab.Controls)
{
var capturedControl = control; //this is necessary
control.Validating += (sender, e) =>
tabControlList.Add(capturedControl);
control.Validated += (sender, e) =>
tabControlList.Remove(capturedControl);
}
}
}
private void Ok_Button_Click(object sender, EventArgs e)
{
if (this.ValidateChildren())
{
_settings.Save();
this.Close();
}
else
{
var unvalidatedTabs = _tabControls.Where(kvp => kvp.Value.Count != 0)
.Select(kvp => kvp.Key);
TabPage firstUnvalidated = unvalidatedTabs.FirstOrDefault();
if (firstUnvalidated != null &&
!unvalidatedTabs.Contains(OptionTabs.SelectedTab))
OptionTabs.SelectedTab = firstUnvalidated;
}
}
I think it's pretty sweet !

Resources