using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Windows.Forms; using System.Xml; using System.Linq; using System.Threading.Tasks; using System.Threading; using AnxSampleBrowser.Resources; namespace AnxSampleBrowser { /// /// WinForm project which let you browse through the ANX samples /// public partial class AnxSampleBrowser : Form { //list of all registered samples private List _sampleAtlas; private SortedList _filteredSamples; private Dictionary _sampleDataControlMap; private CancellationTokenSource _searchCancellationTokenSource; private object _filteredSamplesLock = new object(); private volatile bool _alreadyRunInvoke = false; private int _previousPage = -1; private int _currentPage = 1; private int _pageCount; //default path to the samples folder public String SamplePath { get; private set; } DropDownElement _sortByName = new DropDownElement("name", StringResources.Name); DropDownElement _sortByCategory = new DropDownElement("category", StringResources.Category); DropDownElement _dontFilterAll = new DropDownElement("all", StringResources.All); DropDownElement _selectedCategory = null; /// /// Constructor of the browser /// parses the xml and set up lists /// public AnxSampleBrowser() { SamplePath = "../../Samples/"; InitializeComponent(); //make the form fixed size this.FormBorderStyle = FormBorderStyle.FixedDialog; // Set the MaximizeBox to false to remove the maximize box. this.MaximizeBox = false; // Set the MinimizeBox to false to remove the minimize box. this.MinimizeBox = false; _dSort.Items.Add(_sortByName); _dSort.Items.Add(_sortByCategory); _dSort.SelectedIndex = 0; _dSort.DropDownStyle = ComboBoxStyle.DropDownList; //add default categorie (everything)... _dCategories.Items.Add(_dontFilterAll); //...and select it _dCategories.SelectedIndex = 0; _dCategories.DropDownStyle = ComboBoxStyle.DropDownList; //init sample list _sampleAtlas = new List(); _filteredSamples = new SortedList(SamplesComparer); _sampleDataControlMap = new Dictionary(); Application.Idle += Application_Idle; //load out Samples.xml parseXMl(); addAll(); buildPages(); Size originalSize = _pSamples.Size; int right = 0; int bottom = 0; foreach (Control c in _pSamples.Controls) { right = Math.Max(right, c.Location.X + c.Size.Width); bottom = Math.Max(bottom, c.Location.Y + c.Size.Height); } //_pSamples gets resized by changing the size of the window. this.ClientSize = new System.Drawing.Size(ClientSize.Width + right - originalSize.Width, ClientSize.Height + bottom - originalSize.Height); _cFilter.CheckOnClick = true; for (int i = 0; i < _cFilter.Items.Count; ++i) { _cFilter.SetItemChecked(i, true); } this._cFilter.ItemCheck += _cFilter_ItemCheck; } void _cFilter_ItemCheck(object sender, ItemCheckEventArgs e) { search(); } void Application_Idle(object sender, EventArgs e) { _alreadyRunInvoke = false; } private IComparer SamplesComparer { get { if (_dSort.SelectedItem == _sortByCategory) { return SampleDataCategoryComparer.Instance; } else if (_dSort.SelectedItem == _sortByName) { return SampleDataNameComparer.Instance; } else throw new InvalidOperationException("No sort mode specified."); } } protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; //Activating DoubleBuffered for form and child controls where possible. cp.ExStyle |= 0x02000000; // WS_EX_COMPOSITED return cp; } } private void buildPages() { Action body = () => { //Makes this async code on the main thread more performant by running it only once per update. //It can happen that we trigger the buildPage several times before the app gets idle again, in which //it would usually work through the calls we gave by calling invoke. When that happens, it would run this //code multiple successive times, but in between these calls it doesn't have the chance to manipulate the //data upon which we build these controls. Which means, it would be a wasted effort. //But this check is also from the time were the controls weren't cached and the performance difference isn't //as big anymore as it once was. if (_alreadyRunInvoke) return; bool needsRebuild = false; var controls = _pSamples.Controls.Cast().ToArray(); if (_filteredSamples.Count != 0) { var sourceData = _filteredSamples.GetRange((_currentPage - 1) * 6, Math.Min(6, _filteredSamples.Count - (_currentPage - 1) * 6)); if (controls.Length == sourceData.Count) { for (int i = 0; i < controls.Length; i++) { var data = controls[i].SampleData; if (data != sourceData[i]) { needsRebuild = true; break; } } } else needsRebuild = true; } else needsRebuild = true; if (_previousPage != _currentPage) needsRebuild = true; _previousPage = _currentPage; if (needsRebuild) { _pSamples.Controls.Clear(); _pageCount = (int)Math.Ceiling(_filteredSamples.Count / 6f); _currentPage = Math.Min(_currentPage, _pageCount); if (_currentPage < 1 && _pageCount >= 1) _currentPage = 1; _lCurrentPage.Text = _currentPage.ToString(); _lMaxPage.Text = _pageCount.ToString(); if (_filteredSamples.Count > 0) { for (int i = 6 * (_currentPage - 1); i < _currentPage * 6 && i < _filteredSamples.Count; ++i) { addSampleDataVisual(_filteredSamples[i]); } } } _alreadyRunInvoke = true; }; if (this.InvokeRequired) this.Invoke(body); else { //Just making sure, that if we are running this code not on the idle event, it actually runs. _alreadyRunInvoke = false; body(); _alreadyRunInvoke = false; } } private void parseXMl() { XmlDocument sampleDescription = new XmlDocument(); try { sampleDescription.Load(SamplePath + "SampleCatalog.xml"); } catch (FileNotFoundException) { OpenFileDialog openSampleXML = new OpenFileDialog(); openSampleXML.Filter = "xml files (*.xml)|*.xml"; openSampleXML.Title = "Pick SampleCatalog.xml"; if (openSampleXML.ShowDialog() == DialogResult.OK) { SamplePath = Path.GetDirectoryName(openSampleXML.FileName); sampleDescription.Load(openSampleXML.FileName); } else { MessageBox.Show("Samples.xml not found"); this.Close(); return; } } XmlNode sampleRoot = sampleDescription.ChildNodes[1]; List _categorieAtlas = new List(); List _tagAtlas = new List(); foreach (XmlNode sample in sampleRoot.ChildNodes) { SampleData data = new SampleData() { Name = sample["Name"].InnerText, Description = sample["Description"].InnerText, ExecPath = sample["ExecPath"].InnerText, ProjectPath = sample["ProjectPath"].InnerText, Category = sample["Category"].InnerText, }; XmlNode imagePath = sample["ImagePath"]; if (imagePath != null) data.ImagePath = imagePath.InnerText; else data.ImagePath = ""; data.Validate(); if (!_categorieAtlas.Contains(sample["Category"].InnerText)) _categorieAtlas.Add(sample["Category"].InnerText); XmlNode tags = sample["Tags"]; List dataTags = new List(); foreach (XmlNode tag in tags.ChildNodes) { if (!_tagAtlas.Contains(tag.InnerText)) _tagAtlas.Add(tag.InnerText); dataTags.Add(tag.InnerText); } data.Tags = dataTags.ToArray(); _sampleAtlas.Add(data); addSampleDataVisual(data); } _dCategories.Items.AddRange(_categorieAtlas.Select((categoryName) => new DropDownElement(categoryName, categoryName)).ToArray()); _cFilter.Items.AddRange(_tagAtlas.ToArray()); } private void addAll() { lock (_filteredSamplesLock) { _filteredSamples.Clear(); _filteredSamples.AddRange(_sampleAtlas); buildPages(); } } private void addAll(List value) { lock (_filteredSamplesLock) { _filteredSamples.Clear(); _filteredSamples.AddRange(value); buildPages(); } } private bool isAllowed(SampleData data) { if (_selectedCategory != _dontFilterAll) { if (data.Category != _selectedCategory.Name) return false; } if (data.Tags.Length == 0) return true; foreach (String tag in data.Tags) { if (_cFilter.CheckedItems.Contains(tag)) return true; } return false; } private void addSampleDataVisual(SampleData data) { SampleDataHalfVisual dataV; if (!_sampleDataControlMap.TryGetValue(data, out dataV)) { dataV = new SampleDataHalfVisual(data, this); _sampleDataControlMap.Add(data, dataV); } if (this._pSamples.Controls.Count % 2 == 0) dataV.Location = new Point(0, (this._pSamples.Controls.Count / 2) * (dataV.Size.Height + 5)); else dataV.Location = new Point(dataV.Width + 5, ((this._pSamples.Controls.Count - 1) / 2) * (dataV.Size.Height + 5)); this._pSamples.Controls.Add(dataV); } private Task search() { if (_searchCancellationTokenSource != null) { _searchCancellationTokenSource.Cancel(); } _searchCancellationTokenSource = new CancellationTokenSource(); return Task.Factory.StartNew(() => search(_searchCancellationTokenSource.Token), _searchCancellationTokenSource.Token); } private void search(CancellationToken cancellationToken) { if (!this.IsHandleCreated) return; lock (_filteredSamplesLock) { if (cancellationToken.IsCancellationRequested) return; _filteredSamples.Clear(); //Search by any of the specified words and remove empty entries to make sure that we don't just return everything if we end with one of the split characters. String[] phrases = _tSearch.Text.Split(new char[] { ' ', ',', ';' }, StringSplitOptions.RemoveEmptyEntries); //If we have nothing or just the split characters, we return everything. if (phrases.Length == 0) phrases = new string[] { "" }; foreach (SampleData data in _sampleAtlas) { if (cancellationToken.IsCancellationRequested) return; if (!isAllowed(data)) continue; bool add = false; foreach (string phrase in phrases) { if (data.Name.Contains(phrase, StringComparison.OrdinalIgnoreCase) || data.Description.Contains(phrase, StringComparison.OrdinalIgnoreCase) || data.Category.Contains(phrase, StringComparison.OrdinalIgnoreCase)) { add = true; break; } foreach (String tag in data.Tags) { if (tag.ToLower().Equals(phrase.ToLower())) { add = true; break; } } } if (add) _filteredSamples.Add(data); } buildPages(); } } private void _bSearch_Click(object sender, EventArgs e) { search(); } private void _tSearch_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Enter) { e.SuppressKeyPress = true; e.Handled = true; search(); } } private void _tSearch_TextChanged(object sender, EventArgs e) { search(); } private void _bClear_Click(object sender, EventArgs e) { this._tSearch.Text = ""; addAll(); } private void AnxSampleBrowser_Load(object sender, EventArgs e) { this.Text += " v" + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); } private void _cSort_SelectedIndexChanged(object sender, EventArgs e) { //Make sure our List has been initialized. if (_filteredSamples == null) return; lock (_filteredSamplesLock) { _filteredSamples.Comparer = SamplesComparer; buildPages(); } } private void pictureBox2_Click(object sender, EventArgs e) { _currentPage = _currentPage + 1 > _pageCount ? _currentPage : _currentPage + 1; buildPages(); } private void pictureBox1_Click(object sender, EventArgs e) { _currentPage = _currentPage - 1 <= 0 ? _currentPage : _currentPage - 1; buildPages(); } private void _dCategories_SelectedIndexChanged(object sender, EventArgs e) { _selectedCategory = (DropDownElement)_dCategories.SelectedItem; search(); } } }