WPF PropertyGrid supports multiple selection - c#

Is this documentation still valid or am I missing something?
http://doc.xceedsoft.com/products/XceedWpfToolkit/Xceed.Wpf.Toolkit~Xceed.Wpf.Toolkit.PropertyGrid.PropertyGrid~SelectedObjects.html
PropertyGrid control does not appear to have SelectedObjects or SelectedObjectsOverride members. I'm using the latest version (2.5) of the Toolkit against .NET Framework 4.0.
UPDATE
#faztp12's answer got me through. For anyone else looking for a solution, follow these steps:
Bind your PropertyGrid's SelectedObject property to the first selected item. Something like this:
<xctk:PropertyGrid PropertyValueChanged="PG_PropertyValueChanged" SelectedObject="{Binding SelectedObjects[0]}" />
Listen to PropertyValueChanged event of the PropertyGrid and use the following code to update property value to all selected objects.
private void PG_PropertyValueChanged(object sender, PropertyGrid.PropertyValueChangedEventArgs e)
{
var changedProperty = (PropertyItem)e.OriginalSource;
foreach (var x in SelectedObjects) {
//make sure that x supports this property
var ProperProperty = x.GetType().GetProperty(changedProperty.PropertyDescriptor.Name);
if (ProperProperty != null) {
//fetch property descriptor from the actual declaring type, otherwise setter
//will throw exception (happens when u have parent/child classes)
var DeclaredProperty = ProperProperty.DeclaringType.GetProperty(changedProperty.PropertyDescriptor.Name);
DeclaredProperty.SetValue(x, e.NewValue);
}
}
}
Hope this helps someone down the road.

What i did when i had similar problem was I subscribed to PropertyValueChanged and had a List filled myself with the SelectedObjects.
I checked if the contents of the List where of the same type, and then if it is so, I changed the property in each of those item :
PropertyItem changedProperty = (PropertyItem)e.OriginalSource;
PropertyInfo t = typeof(myClass).GetProperty(changedProperty.PropertyDescriptor.Name);
if (t != null)
{
foreach (myClass x in SelectedItems)
t.SetValue(x, e.NewValue);
}
I used this because i needed to make a Layout Designer and this enabled me change multiple item's property together :)
Hope it helped :)
Ref Xceed Docs

Related

PropertyGrid - Specify ExpandableObject if I don't have control of the class

I try to use a PropertyGrid (actually, it's the xceed wpf toolkit propertygrid, but I can switch to the standard forms PropertyGrid if that makes it easier), and the object I need to show in the grid has some child-objects that I need to be able to expand.
I found out I can achieve this by marking the properties with the "ExpandableObject" attribute. However, in some cases I am not the author of the class (or I am, but don't want to clutter it with GUI-stuff), so I cannot add attributes like that.
Is there any other way to tell the PropertyGrid which properties that should be expandable?
Xceed property grid has an event PreparePropertyItem. You can handle it and set e.PropertyItem.IsExpandable property. There is an example of handler that makes all non primitive properties expandable:
private void propertyGrid_PreparePropertyItem(object sender, Xceed.Wpf.Toolkit.PropertyGrid.PropertyItemEventArgs e)
{
var item = e.Item as Xceed.Wpf.Toolkit.PropertyGrid.PropertyItem;
if (item == null)
return;
if (!item.PropertyType.IsValueType && item.PropertyType != typeof(string))
{
e.PropertyItem.IsExpandable = true;
}
}

WPF - setting properties to themselves fails to fire changed event

Here's a curious piece of behaviour. We recently built some code in a WPF MVVM application that looked a bit like this:
foreach (var mA in Preferences.Where(itm => itm.Preference == "Y"))
{
Member m = _members.FirstOrDefault(itm => itm.MemberID == mA.MemberAvertedID);
if (m != null)
{
m.Selected = true;
}
}
Members = _members;
So, FirstOrDefault fetches a reference to a Member, which is updated. Members and _members are effectively the same - the former is a property, wrapping the latter as a private variable, with an event fire:
public ObservableCollection<Member> Members
{
get
{return _members;}
set
{
_members = value;
OnPropertyChanged("Members");
}
}
The purpose of setting Members to _members was simply to get the event to fire - but it didn't work. As you stepped through, the OnPropertyChanged event fired, but the application didn't respond. This, however, does work:
foreach (var mA in Preferences.Where(itm => itm.Preference == "Y"))
{
Members m = _members.FirstOrDefault(itm => itm.MemberID == mA.MemberAvertedID);
if (m != null)
{
mtc.Selected = true;
}
}
var mem = new ObservableCollection<Members>(_members);
Members = mem;
I'm assuming that what's going on here is that because setting Members to _members is effectively the property setting itself, the code "presumes" that nothing has changed, and skips the event. But I'm not really satisfied with that explanation. Can anyone elucidate further as to what's going on here?
Bindings to ObservableCollections will not rebind the list unless the collection reference differs, hence the last piece of code is working. That said, unless you are actually adding or removing items from the underlying _members list you shouldn't have to rebind the whole list.
So (I assume) if your goal is to refresh the state of the Selected Member, you are probably lacking a OnPropertyChanged("Selected") raised from within the Selected property.
In summary: the property that is changing must be named in the PropertyChanged event. And the event must come from the object that owns the property. In this case, notifying WPF of a change to the Selected property requires raising the event on the Member instance.

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);

Combobox does not change value when a different value is selected

I am trying to migrate a small prototype application I made in WinForms to WPF. I'm having some issues with a combobox in WPF not changing values when I select a different value from the drop-down. Initially, I tried just copying the code that I used in my WinForms app to populate the combobox and determine if a new index had been selected. This is how my WinForms code looked like:
private void cmbDeviceList_SelectedIndexChanged(object sender, EventArgs e)
{
var cmb = (Combobox) sender;
var selectedDevice = cmb.SelectedItem;
var count = cmbDeviceList.Items.Count;
// find all available capture devices and add to drop down
for(var i =0; i<count; i++)
{
if(_deviceList[i].FriendlyName == selectedDevice.ToString())
{
_captureCtrl.VideoDevices[i].Selected = true;
break;
}
}
}
Earlier in the code, I am populating the _deviceList List and the combo box (in Form1_Load to be specific) by looping over the available devices and adding them. I tried the same approach in WPF and could only populate the combo box. When I selected a new value, for some reason the same exact value (the initial device) was being sent into the event code (cmbCaptureDevices_SelectionChanged in my WPF app). I looked around for some tutorials in WPF and found that maybe data binding was my issue, and I tried that out instead. This is my combobox in my XAML file:
<ComboBox ItemsSource="{Binding Devices}" Name="cmbCaptureDevices"
IsSynchronizedWithCurrentItem="True" SelectedItem="{Binding CurrentDevice,
Mode=TwoWay}" Se;ectionChanged="cmbCapturedDevices_SelectionChanged" />
There's more to that XAML definition, but it's all arbitrary stuff like HorizontalAlignment and whatnot. My VideoDevicesViewModel inherits from INotifyPropertyChanged, has a private List<Device> _devices and a private Device _currentDevice. The constructor looks like:
public VideoDevicesViewModel()
{
_devices = GetCaptureDevices();
DevicesCollection = new CollectionView(_devices);
}
GetCaptureDevices simply is the loop that I had in my WinForms app which populates the list with all avaialble capture devices on the current machine. I have a public CollectionView DevicesCollection { get; private set; } for getting/setting the devices at the start of the application. The property for my current device looks like:
public Device CurrentDevice
{
get { return _currentDevice; }
set
{
if (_currentDevice = value)
{
return;
}
_currentDevice = value;
OnPropertyChanged("CurrentDevice");
}
}
OnPropertyChanged just raises the event PropertyChanged if the event isn't null. I'm new to WPF (and pretty new to C# in general, honestly) so I'm not sure if I'm missing something elementary or not. Any idea as to why this combobox won't change values for me?
Discovered the answer on my own here. The unexpected behavior was a result of using the Leadtools Device class. It's a COM component and apparently was not playing nicely with my application. I honestly don't understand why exactly it worked, but I wrapped the Device class in another class and used that instead. As soon as I was using the wrapper class, the combo box functioned as it should.
You are using the assignment operator '=' instead of the equality operator '=='
Change
if (_currentDevice = value)
to
if (_currentDevice == value)
Try the following
if _currentDevice == value ...

How can I make a specific TabItem gain focus on a TabControl without click event?

How can I tell my TabControl to set the focus to its first TabItem, something like this:
PSEUDO-CODE:
((TabItem)(MainTabControl.Children[0])).SetFocus();
How about this?
MainTabControl.SelectedIndex = 0;
this.tabControl1.SelectedTab = this.tabControl1.TabPages["tSummary"];
I've found it's usually a best practice to name your tabs and access it via the name so that if/when other people (or you) add to or subtact tabs as part of updating, you don't have to go through your code and find and fix all those "hard coded" indexes. hope this helps.
I realise this was answered a long time ago, however a better solution would be to bind your items to a collection in your model and expose a property that selected item is bound to.
XAML:
<!-- MyTemplateForItem represents your template -->
<TabControl ItemsSource="{Binding MyCollectionOfItems}"
SelectedItem="{Binding SelectedItem}"
ContentTemplate="{StaticResource MyTemplateForItem}">
</TabControl>
Code Behind:
public ObservableCollection<MyItem> MyCollectionOfItems {
get;
private set;
}
private MyItem selectedItem;
public MyItem SelectedItem{
get { return selectedItem; }
set {
if (!Object.Equals(selectedItem, value)) {
selectedItem = value;
// Ensure you implement System.ComponentModel.INotifyPropertyChanged
OnNotifyPropertyChanged("SelectedItem");
}
}
}
Now, all you have to do to set the item is:
MyItem = someItemToSelect;
You can use the same logic with the SelectedIndex property, further, you can use the two at the same time.
This approach allows you to separate your model correctly from the UI, which could allow you to replace the TabControl with something else down the line but not requiring you to change your underlying model.
Look at the properties for the tab control...
Expand the TabPages properties "collection"...
Make note of the names you gave the members.
ie. a tab control called tabMain with 2 tabs called tabHeader and tabDetail
Then to select either tab...You have to set it with the tabname
tabMain.SelectedTab = tabHeader;
tabControl1.SelectedTab = item;
item.Focus();
Basically all of the answers here deal with SELECTION, which does not answer the question.
Maybe that is what OP wanted, but the question very specifically asks for FOCUS.
TabItem item = (TabItem)MainTabControl.Items[0];
// OR
TabItem item = (TabItem)MainTabControl.SelectedItem;
// Then
item.Focus();
tabControl.SelectedItem = tabControl.Items[0];
If you have a Tabcontroller named tabControl you could set the selectedIndex from different methods, i use following methods mostly.
codebehind:
tabControl.SelectedIndex = 0; // Sets the focus to first tabpanel
clientside:
First, put the following javascript in your aspx/ascx file:
<script type="text/javascript">
function SetActiveTab(tabControl, activeTabIndex) {
var activeTab = tabControl.GetTab(activeTabIndex);
if(activeTab != null)
tabControl.SetActiveTab(activeTab);
}</script>
Then add following clientside event to prefered controller:
OnClientClick="function(s, e) { SetActiveTab(tabControl, 0);
it's better to use the following type of code to select the particular
item in the particular tab...
.
private void PutFocusOnControl(Control element)
{
if (element != null)
Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Input,
(System.Threading.ThreadStart)delegate
{
element.Focus();
});
}
And in calling time... tabcontrol.isselected=true;
PutFocusOnControl(textbox1);
will works fine...
Private Sub TabControl1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles TabControl1.SelectedIndexChanged
'MsgBox(TabControl1.SelectedIndex)
If TabControl1.SelectedIndex = 0 Then
txt_apclntFrstName.Select()
Else
txtApplcnNo.Select()
End If
End Sub
It worked for me to set focus to the last tab just after I open it:
//this is my assignment of the collection to the tab control
DictTabControl.DataContext = appTabs.DictTabs;
//set the selected item to the last in the collection, i.e., the one I just added to the end.
DictTabControl.SelectedItem = DictTabControl.Items[(DictTabControl.Items.Count-1)];

Categories