Yes, i know its been asked a bazillion times, I've looked through, and tried most of those answers. for context, I'm creating a dock-able pane within an API (Revit). Most solutions are based on the Windows class, but I'm forced to use a Page class for the form. My data is gathered from two lists parsed through a custom class and sorted into one list, then i had the brilliant idea of mapping it to a dictionary, with one of the list properties as its key. Displaying the dictionary in a treeview was nice an easy, but as you can see checking the Parent does not check all its children:
So id like to keep the dictionary if possible, but am fully prepared to restructure my list as needed to get this working. To summarize the ask:
- Keep the data structured as as a Dictionory if possible.
- Be able to check all children when parent is checked, but parent should not be checked (i do not require a three state) when children are checked.
- Checkbox and item are separate controls, such that user can select an item, which will display and image without checking the checkbox.
What i've tried; recursive functions and Revit do not mix, so mapping my list to the treeview that way is out. Binding the IsChecked of the child to the parent and vice versa. I think this fails due to the way the xaml is structured to use the dictionary. And a slew of other things i cant think of right now.
Anyways, heres the XAML:
<Page x:Class="Typ_Detail_Selector.TypDetForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
Title="Typical Detail Selector" Foreground="{x:Null}" MinWidth="100" MinHeight="400" Background="{DynamicResource {x:Static SystemColors.ControlDarkBrushKey}}">
<StackPanel HorizontalAlignment="Stretch" Margin="5,5,5,5" VerticalAlignment="Stretch">
<Label Content="Views" HorizontalAlignment="Left" Margin="0,0,0,0" VerticalAlignment="Top"/>
<TreeView x:Name="TreeViewDets" HorizontalAlignment="Left" Height="400" MinWidth="400" SelectedItemChanged="TreeViewDets_SelectedItemChanged">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Value}">
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False" IsChecked="{Binding Checked}" Click="CheckPleaseParent"/>
<TextBlock Text="{Binding Key}"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox Focusable="False" IsChecked="{Binding Checked}" Click="CheckPleaseChild"/>
<TextBlock Text="{Binding Name}"/>
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate.ItemContainerStyle>
<Style TargetType = "TreeViewItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Loaded}" Value="true">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Foreground" Value="DimGray"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=Loaded}" Value="false"/>
<Condition Binding="{Binding Path=Checked}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter Property="Background" Value="SteelBlue"/>
<Setter Property="Foreground" Value="Azure"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</HierarchicalDataTemplate.ItemContainerStyle>
<HierarchicalDataTemplate.Resources>
<Style TargetType="{x:Type CheckBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Loaded}" Value="true">
<Setter Property="IsEnabled" Value="False"/>
<Setter Property="Foreground" Value="DimGray"/>
</DataTrigger>
</Style.Triggers>
</Style>
</HierarchicalDataTemplate.Resources>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
<Grid>
<Button x:Name="RunButton" Content="Insert Views" HorizontalAlignment="Right" VerticalAlignment="Bottom" Width="180" Height="25" Click="Run_Click"/>
</Grid>
</StackPanel>
</Page>
And the xaml.cs, i must stress this is for API work, so theres a lot of extra things not specific to the WPF itself:
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media.Imaging;
using System.Windows.Controls;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
using Autodesk.Revit.DB.Events;
using View = Autodesk.Revit.DB.View;
using Application = Autodesk.Revit.ApplicationServices.Application;
using Transform = Autodesk.Revit.DB.Transform;
using System.Reflection;
using System.Collections;
namespace Typ_Detail_Selector
{
public partial class TypDetForm : Page, IDockablePaneProvider
{
private TypDetailRegister TypDetViews;
public Document TypDoc;
private Document ThisDoc;
private Application App;
private UIApplication UiApp;
private Dictionary<string, List<ViewProperties>> viewDict = new Dictionary<string, List<ViewProperties>>();
public TypDetForm()
{
InitializeComponent();
}
public void SetupDockablePane(DockablePaneProviderData data)
{
data.FrameworkElement = this;
data.InitialState = new DockablePaneState();
data.InitialState.DockPosition = DockPosition.Right;
}
public void ReferenceItems(TypDetailRegister _typdetviews, Document _typdoc, Document _thisdoc, Application _app)
{ //this is to set references to things from command class
TypDoc = _typdoc;
ThisDoc = _thisdoc;
TypDetViews = _typdetviews;
App = _app;
UiApp = new UIApplication(App);
UiApp.Idling += TypDetViews.TwiddleThumbs;
viewDict = TypDetViews.ViewList().GroupBy(x => x.SheetName).ToDictionary(x => x.Key, x => x.ToList());
RefreshCompare();
}
public void RefreshCompare()
{//compares list of views an schedules in the current project to the typ det file,
//flags them as "loaded" so you cant re import them later.
List<ViewProperties> _tempviewprops = TypDetViews.ViewList();
List<Element> _compareViewList = new FilteredElementCollector(ThisDoc).OfClass(typeof(ViewDrafting))
.Where(v => v.Name != "MKA Starting View").ToList();
List<Element> _compareSchedList = new FilteredElementCollector(ThisDoc).OfClass(typeof(ViewSchedule))
.Where(s => !s.Name.ToUpper().Contains("REVISION"))
.Where(s => !s.Name.Contains("DRAWING LIST"))
.ToList();
_compareViewList.AddRange(_compareSchedList);
foreach (ViewProperties v in TypDetViews.ViewList())
{
var _viewExists = from views in _compareViewList
where v.Name == views.Name
select v;
if (_viewExists.Any())
{
v.Loaded = true;
v.Checked = true;
}
else
{
v.Loaded = false;
v.Checked = false;
}
}
TreeViewDets.ItemsSource = null;
TreeViewDets.ItemsSource = viewDict;
}
private void ListViewDets_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ //handler for when user selects (but not checks!) and item in teh list
string imagelibrary = @"K:\Development\_Typical Detail Change Tracking\Base Models\Typ Images";
if (e.AddedItems.Count > 0) //check if something was in fact selected
{
//dim teh item as a member of the list class to get at certain properites
ViewProperties test = e.AddedItems[0] as ViewProperties;
try
{
//replace characters that windows automatically replaces for the png files
string view_image = imagelibrary + "\\" + test.Name.Replace("\"", "-").Replace("/", "-") + ".png";
if (File.Exists(view_image))//if an image file exists in teh library
{ //add it to to image control on the form as preview.
BitmapImage bmp = new BitmapImage(new Uri(view_image, UriKind.Absolute));
// PreviewImage.Source = bmp;
}
}
catch { }
e.AddedItems.Clear();//clear it so program does not get confused the next time handler is triggered.
}
}
private void CheckPleaseChild(object sender, RoutedEventArgs e)
{ //seperate handler to change the checkbox, and ping the selection changed event
var cb = sender as CheckBox;
var item = cb.DataContext;
ListViewDets.SelectedItem = item;
}
private void CheckPleaseParent(object sender, RoutedEventArgs e)
{ //seperate handler to change the checkbox, and ping the selection changed event
var cb = sender as CheckBox;
var iteam = cb.DataContext;
PropertyInfo[] testprops = cb.DataContext.GetType().GetProperties();
List<ViewProperties> testlist = new List<ViewProperties>();
testlist = (List<ViewProperties>)testprops.Last().GetValue(cb.DataContext, null);
foreach (var temp in testlist)
{
//need to trigger checkpleasechild somehow?
}
}
private void TreeViewDets_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
TreeView tv = sender as TreeView;
var item = tv.Items.CurrentItem; //this gets me the KVP, same as in checkpleaseparent
}
private void ListViewDets_SizeChanged(object sender, SizeChangedEventArgs e)
{ //this is to stretch the listview with the form
ListView tempview = sender as ListView;
GridView gview = tempview.View as GridView;
var defaultwidth = tempview.ActualWidth - SystemParameters.VerticalScrollBarWidth;
var percent = 0.95;
gview.Columns[1].Width = defaultwidth * percent;
}
private void Run_Click(object sender, RoutedEventArgs e)
{ //clicking run, sets the bool, which in trun runs the InsertViews when able (idling)
GlobalVars.DoTheThings = true;
}
public void InsertViews(ExternalCommandData commdata)
{
GlobalVars.DoTheThings = false;//reset bool
App.FailuresProcessing += HideDupeMessages;//to deal with dupilcate names dialog
ExternalCommandData _commdata = commdata;
//grab all the element ids of items that are checked in the list, but not loaded, which are the views we want to insert
ICollection<ElementId> _viewIds = TypDetViews.ViewList().Where(x => x.Checked && !x.Loaded).Select(y => y.ViewId).ToList();
ICollection<ElementId> _copiedViewIds = null;
Document _doc = _commdata.Application.ActiveUIDocument.Document;
ThisDoc = _doc;
using (TransactionGroup tg = new TransactionGroup(_doc)) //trans group to keep the undo list clean
{
tg.Start();
using (Transaction trView = new Transaction(_doc))
{
foreach (ElementId _viewId in _viewIds)
{
ICollection<ElementId> _vId = _viewIds.Where(x => x == _viewId).ToList(); //annoying API thing, must put the element id in an Icollection, else it doesn work
bool test = TypDetViews.ViewList().Where(x => x.ViewId == _vId.First()).Select(y => y.SchedTest).First();//is it a scehdule?
trView.Start("Copy Typ Views to Project");
{
CopyPasteOptions opts = new CopyPasteOptions();
opts.SetDuplicateTypeNamesHandler(new DupeNamesHandler());
try
{ //add the elementid of the view or sched that was just inserted to a empty collection
_copiedViewIds = ElementTransformUtils.CopyElements(TypDoc, _vId, _doc, Transform.Identity, opts);
}
catch { }
trView.Commit();
}
//if its not a sched, grab every element in teh view,
//bypass for scheds, as it grabs the modeled elements in the sched(errors out for stickies)
if (test)
{
ICollection<ElementId> _nestedEelemIds = new FilteredElementCollector(TypDoc, _viewId)
.WherePasses(new ElementCategoryFilter(ElementId.InvalidElementId, true))
.ToElementIds();
//if we foudn elements, copy them from the typ view to the new view we just inserted
if (_nestedEelemIds.Count > 0)
{
View _sourceView = TypDoc.GetElement(_viewId) as View;
View _targetView = _doc.GetElement(_copiedViewIds.First()) as View;
using (Transaction trElements = new Transaction(_doc))
{
trElements.Start("Copy Typ Det Elements to Project View");
CopyPasteOptions opts = new CopyPasteOptions();
opts.SetDuplicateTypeNamesHandler(new DupeNamesHandler());
ElementTransformUtils.CopyElements(_sourceView, _nestedEelemIds, _targetView, Transform.Identity, opts);
trElements.Commit();
}
}
}
_copiedViewIds.Clear();//clear teh list for the next run.
}
}
tg.Assimilate();
}
//write to a text file
try
{
string log_file = "Typical Detail Selector.txt";
string text = "";
string date = DateTime.Now.Year.ToString() + "." + DateTime.Now.Month.ToString() + "." + DateTime.Now.Day.ToString();
string user = Environment.UserName;
string other = "Run";
text = date + " ~ " + user + " ~ " + other;
StreamWriter sw = File.AppendText("J:/CADD/Customization/Add-In Usage/" + log_file);
sw.WriteLine(text);
sw.Close();
}
catch
{
}
RefreshCompare();//refresh the lsit when we done inserting views.
}
public void SetActiveDocument()
{
ThisDoc = UiApp.ActiveUIDocument.Document;
RefreshCompare();
}
public void NewDocCity(object sender, DocumentOpenedEventArgs e)
{
SetActiveDocument();
}
//public PreviewControl PrevCon { get; set; }
private void HideDupeMessages(object sender, FailuresProcessingEventArgs e)
{
FailuresAccessor FA = e.GetFailuresAccessor();
foreach (FailureMessageAccessor fmsg in FA.GetFailureMessages())
{
if (fmsg.GetSeverity() == FailureSeverity.Warning)
{
FA.DeleteWarning(fmsg);
e.SetProcessingResult(FailureProcessingResult.ProceedWithCommit);
}
}
e.SetProcessingResult(FailureProcessingResult.Continue);
}
}
public class DupeNamesHandler : IDuplicateTypeNamesHandler
{
public DuplicateTypeAction OnDuplicateTypeNamesFound(DuplicateTypeNamesHandlerArgs args)
{
return DuplicateTypeAction.UseDestinationTypes;
}
}
}
Aucun commentaire:
Enregistrer un commentaire