I have created a simple C# Windows 8 grid application.
If you're unfamiliar with this layout, there is a brief explanation of it here :
Link
What I would like to have is simple - some custom ItemDetailPages. I'd like to be able to click on some items on the GroupDetailPage and the GroupedItemsPage and navigate to a custom .xaml file, one where I can include more than one image.
I'm sure there is a simple way of doing that that I have missed out on, and I'm also sure that this information will be useful for a lot of people, so I will be offering a bounty on this question.
I have struggled with doing this so far :
I've created a CustomDataItem in the SampleDataSource.cs class :
/// <summary>
/// Generic item data model.
/// </summary>
public class CustomDataItem : SampleDataCommon
{
public CustomDataItem(String uniqueId, String title, String subtitle, String imagePath, String description, String content, SampleDataGroup group)
: base(uniqueId, title, subtitle, imagePath, description)
{
this._content = content;
this._group = group;
}
private string _content = string.Empty;
public string Content
{
get { return this._content; }
set { this.SetProperty(ref this._content, value); }
}
private SampleDataGroup _group;
public SampleDataGroup Group
{
get { return this._group; }
set { this.SetProperty(ref this._group, value); }
}
}
However, obviously, adding to the ObservableCollection
private ObservableCollection<SampleDataGroup> _allGroups = new ObservableCollection<SampleDataGroup>();
public ObservableCollection<SampleDataGroup> AllGroups
{
get { return this._allGroups; }
}
is impossible with a different data type. So what can I do in this case ?
Thanks a lot.
I have a simple grid application; how do I make it possible to have one of the elements in the group item page link to a custom item detail page ?
Ok, lets take the app that is generated when using the "Grid App" template from Visual Studio.
The data class for the elements on the group items page is the SampleDataItem class. What you can do is add some type of data field (bool, int, or other) that indicates how to handle the navigation. In this example, we are keeping it simple, so we add a bool to indicate whether the navigation is custom or not.
public class SampleDataItem : SampleDataCommon
{
// add flag as last param
public SampleDataItem(String uniqueId, String title, String subtitle,
String imagePath, String description, String content, SampleDataGroup group,
bool isCustomNav = false)
: base(uniqueId, title, subtitle, imagePath, description)
{
this._content = content;
this._group = group;
this.IsCustomNav = isCustomNav;
}
// to keep it simple this doesn't handle INotifyPropertyChange,
// as does the rest of the properties in this class.
public bool IsCustomNav { get; set; }
...
}
So when you are adding a new SampleDataItem object to be displayed, you just need to set the isCustomNav field in the constructor.
Now all we have to do is change the already existing click event handler in the grid on the grouped item page (GroupedItemsPage.xaml.cs):
void ItemView_ItemClick(object sender, ItemClickEventArgs e)
{
// Navigate to the appropriate destination page, configuring the new page
// by passing required information as a navigation parameter
var item = (SampleDataItem)e.ClickedItem;
var itemId = item.UniqueId;
if (item.IsCustomNav == false)
{
// default
this.Frame.Navigate(typeof(ItemDetailPage), itemId);
}
else
{
// custom page
this.Frame.Navigate(typeof(ItemDetailPage2), itemId);
}
}
All we are doing above is getting the selected item and then testing the navigation flag that we added earlier. Based on this we navigate to either the original ItemDetailPage or a new one called ItemDetailPage2. As I mentioned before, the navigation flag doesn't have to be a bool. It can be an int or enum or some other type that tells us where to navigate.
Note that if you want similar behavior on the GroupDetailsPage, you just have to update the click event handler there the same way.
Hope that helps.
Yes you should be able to create a custom or different data type. If you create a Win8 app using the grid template, you see that the template does three things for you:
1) It creates three types, SampleDataCommon, which is the base, SampleDataItem, which implements SampleDataCommon and adds two new properties - content and group, and SampleDataGroup which also implements SampleDataCommon, adds a method, ItemsCollectionChanged, and adds two properties, Items and TopItems.
2) It creates a class called SampleDataSource, in which a collection of SampleDataGroup is created and named AllGroups: ObservableCollection AllGroups.
3) It binds Items and AllGroups of SampleDataSource to objects in XMAL pages.
In your case, you use the same data structure. In other words, you will create a group with items, etc.
Related
I am trying to implement mvvm and I am struggling with a clean solution to a simple problem. The view can have 3 different states: NewEditable, DisplayOnly, Editable. The idea is that at each state a label and content of a button will change depending on a state, for example, the button will be: "Add", "Edit" and "Save" respectively.
Currently, the vm has bindable properties that update depending on the state of the control but this seems very messy especially when the rest of the logic for the vm is added. Like, it is just playing with strings.
Is there a better, cleaner way? Maybe couple converters that would take a state as input and string as output?
How do you approach changing views based on the state of a vm?
My current ViewModel just for the view logic as you can see loads of boilerplate:
public class ViewModel : NotifyPropertyChangedBase
{
public enum State { New, Edit, DisplayOnly }
public ViewModel()
{
// Set commands
Edit = new CommandHandler(param => EditAction(), () => true);
EndEdit = new CommandHandler(param => EndEditAction(), () => true);
/*
* Some more logic to set up the class
*/
}
public ICommand Edit { get; private set; }
public ICommand EndEdit { get; private set; }
public State DisplayState
{
get { return _displayState; }
private set { SetProperty(ref _displayState, value, nameof(DisplayState)); } // from the base to simply the logic
}
public string ControlTitle
{
get { return _controlTitle; }
private set { SetProperty(ref _controlTitle, value, nameof(ControlTitle)); } // from the base to simply the logic
}
public string ButtonTitle
{
get { return _buttonTitle; }
private set { SetProperty(ref _buttonTitle, value, nameof(ButtonTitle)); } // from the base to simply the logic
}
private State _displayState = State.New;
private string _controlTitle = CONTROL_TITLE_NEW;
private string _buttonTitle = BUTTON_TITLE_NEW;
public const string CONTROL_TITLE_NEW = "New Object"; // Could be removed as used once in the example
public const string CONTROL_TITLE_DISPLAY = "Display Object";
public const string CONTROL_TITLE_EDIT = "Edit Object";
public const string BUTTON_TITLE_NEW = "Create"; // Could be removed as used once in the example
public const string BUTTON_TITLE_DISPLAY = "Edit";
public const string BUTTON_TITLE_EDIT = "Save";
private void EditAction()
{
DisplayState = State.Edit;
ControlTitle = CONTROL_TITLE_EDIT;
ButtonTitle = BUTTON_TITLE_EDIT;
// Some business logic
}
private void EndEditAction()
{
DisplayState = State.DisplayOnly;
ControlTitle = CONTROL_TITLE_DISPLAY;
ButtonTitle = BUTTON_TITLE_DISPLAY;
// Some business logic
}
/*
* Rest of the logic for the class
*/
}
There is multiple approaches to this problem.
Easiest would be to have 2 properties on viewmodel: ButtentText and LabelText. Both returns value which is binded to UI and uses switch inside to select what text it should be.
'More correct' approach would be to have 2 converters, which would basically do same thing: convert enum to some kind display value (button and label). I wouldn't suggest this ways, as first approach is simplier.
Having 3 viewModels for each state looks nice, but just with idea, that in future different things will happen in these viewModels and they will grow apart. If not - it is overkill. And if future will say different, you can always change implementation
I would choose 1st solution, unless your viewModel is +500 rows and it is hard to maintain
If you want to change views depending on viewmodels, I would suggest to read about DataTemplate and DataType. But in this approach would be 1 parent viewModel, which holds what state it should show, and 3 child viewModels. Then you create parentView and inside control with binded current viewModel (one of 3) and with datatypes it will display correct view
I would split your VM into 4, "the common functionality VM" and the 3 for your distinct states, so that you don't have to write switches to change the state. Instead, you would switch between VM's (and possibly between views).
This is a little bit abstract:
I have a website written in ASP.NET MVC. The end user can create a Menu object that will be displaced in the menu of the site. This looks something like this:
public class Menu
{
public string Caption { get; set; }
public string Address { get; set; }
public Menu(string Caption, string Address)
{
this.Caption = Caption;
this.Address = Address;
}
}
public Menu Menu1 = new("Home", "/");
public Menu Menu2 = new("Events", "/events");
public Menu Menu3 = new("Specific Event", "/event/13");
When a request comes in, I need to be able to determine which menu item is "Active". In addition, if a request is for a "subpage" of an item which is not explicitly named, the menu item should also be marked as active (e.g. if the request is for "/event/95/checkout", then Menu2 would be marked as active).
I was thinking about trying to figure compare the relative URL with the Address of the URLs to determine which matches the most characters, starting at the beginning of the string - but how would I go about doing that? Is there perhaps a better way?
We have a similar thing in our code. Our master page has a menu on the left, but one menu item can correlate to a range of sub pages.
The solution we went with was for each menu item to hold a list of regular expressions.
I'll give you a bunch of snippets of our code. This isn't a self-contained solution, but may give you ideas...
We would define our menu using a builder pattern (notice the method UrlMatchPattern)
var builder = new SiteMapBuilder();
builder.Add
.Title("Home")
.LaunchUrl("~/Home/Index")
.IconUrl("~/Images/menuIcons/home.png")
.UrlMatchPattern("~/Home/Index/?.*");
builder.Add
.Title("Network")
.IconUrl("~/Images/menuIcons/network.png")
.Children(b =>
{
b.Add.Title("Companies")
.LaunchUrl("~/Company")
.UrlMatchPattern("~/Company");
b.Add.Title("Groups")
.LaunchUrl("~/PlayerGroup")
.UrlMatchPattern("~/PlayerGroup")
.LimitToRoles(
CmsUserRoleId.Administrators,
CmsUserRoleId.Support
);
b.Add.Title("Players")
.LaunchUrl("~/Player/index")
.UrlMatchPattern(
#"~/\bPlayer\b",
"~/Player/Index",
#"~/Admin/PlayerDiagnostics\.aspx"
);
This builder would dynamically rebuild the menu on each page request, generating a bunch of navigation panel nodes:
internal NavPanelNode GenerateNode(IWebContext webContext, ...)
{
return new NavPanelNode
{
Title = _title,
Url = _url,
Children = _childNodeBuilder == null ? Enumerable.Empty<NavPanelNode>() : _childNodeBuilder.GenerateNodeHierachy(webContext, userContext),
IsSelected = _urlMatchPattern.Any(pattern => webContext.MatchesPath(pattern)),
IconUrl = _iconUrl
};
}
And my MatchesPath method:
public static bool MatchesPath(this IWebContext webContext, System.Text.RegularExpressions.Regex matchPattern)
{
string appRelativePath = webContext.CurrentRequestContext.GetAppRelativePath();
return matchPattern.IsMatch(appRelativePath);
}
As mentioned, our matching logic is via regular expressions.
Of course, if you're making this end-user configurable, you will have to let them configure the matching logic. I don't know how you will do this - it depends upon the user interface you're creating.
The data binding works as it I intend, kind of... The real issue I'm running into now is what I believe to be 2 different instances of my User Control, but only the original, debug list I implemented is showing.
In short, I am building 2 lists that are technically bound to the data grid, the default debugging list I created in the default constructor and then the real list I created to bind to the data grid.
Every time I click on the user control with the data grid, the default constructor adds another line to my debugging list and displays it on the screen.
Every time I click the button that builds a list of selected options on a separate user control I can see my the options add on to the list of options I had been creating and technically set it to the data context of the data grid, the same way the default debug list does, except when I click back over to the data grid user control, the default constructor runs again, ads another line to my debug list, and displays the debug list that is being built.
Here's a copy of the class with a couple lines I added to help debug the problem.
public partial class QuotePreview : UserControl
{
private SelectionList _selectionList;
private SelectionList temp;
public QuotePreview()
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
}
private void QuotePreview_Loaded(object sender, RoutedEventArgs e)
{
//Adds item to Debugging list
_selectionList.SelectedOptions.Add(
new Selection
{
ModelNumber = "this",
Description = "really",
Price = "sucks"
});
}
public QuotePreview(SelectionList selectedOptions)
{
InitializeComponent();
_selectionList = (SelectionList)this.DataContext;
temp = selectedOptions;
//The list I am actually trying to display
_selectionList.AddRange(selectedOptions);
QuotePreview_Loaded();
}
private void QuotePreview_Loaded()
{
foreach (var options in temp.SelectedOptions)
{
_selectionList.SelectedOptions.Add(options);
}
QuotePreviewDataGrid.ItemsSource = _selectionList.SelectedOptions;
}
}
The implementation of the default constructor, is called every time the user control / tab, is clicked on. When that happens, _selectionList is set to the data context of the user control, followed by the Loaded Event which adds a line to my data grid.
In another user control where I select the options I want to add to my data grid user control, I click a button that creates a list of the options I want to be added and calls the custom constructor I wrote. Once the constructor finishes, it calls a custom Loaded Event method that I created for shits and giggles, that adds the selected options to my _selectionList.
Now once I click on the data grid user control again, it goes through the whole default process, and adds another default line.
If I go back a tab and say I want these options again and go back to the data grid, it again goes through the default process and adds another default line.
Whats most intriguing though is that I can see both of the selectionLists build since I dont clear the in between processes. I see a list build of the options i want to display and a list build of the default options build...
Oh, also, SelectionList does implement ObservableCollection
i don't follow exactly what you are asking but loaded event will fire whenever load is required and in your case you are switching between the views , TabControl will not render its content until it is required !
bool _isDefaultItemAdded = false
private void QuotePreview_Loaded(object sender, RoutedEventArgs e)
{
if(!_isDefaultItemAdded)
{
//Adds item to Debugging list
_selectionList.SelectedOptions.Add(
new Selection
{
ModelNumber = "this",
Description = "really",
Price = "sucks"
});
_isDefaultItemAdded = true;
}
}
I finally came up with a solution to the problem.
public static class QuotePreview
{
public static ObservableCollection<PurchasableItem> LineItems { get; private set; }
static QuotePreview()
{
LineItems = new ObservableCollection<PurchasableItem>();
}
public static void Add(List<PurchasableItems> selections)
{
foreach (var selection in selections)
{
LineItems.Add(selection);
}
}
public static void Clear()
{
LineItems.Clear();
}
}
public class QuoteTab : TabItem
{
public ObservableCollection<PurchasableItem> PreviewItems { get; private set; }
public QuoteTab()
{
Initialize()
PreviewItems = QuotePreview.LineItems;
DataGrid.ItemSource = PreviewItems
}
}
I have looked at a lot of places and I'm struggling to do this supposedly simple thing. I have a Windows form on which I've to show a simple DataGridView in this form:
| (CheckBoxColumn) | FilePath | FileState |
Now, there are a couple of problems. The data I need to bind to them is in a List of objects like this:
class FileObject
{
string filePath;
string fileState;
}
//Now here's the list of these objects which I populate somehow.
List<FileObject> listFiles;
Is there any efficient way to bind this directly to the DataGridView
so that different members of Object in the list are bound to
different columns, and for each there's checkbox?
I saw the examples of adding checkbox column to a datagrid, but none of them showed how
to add it to the 'header' row as well, so that it can behave as a 'check all'/'uncheck all' box.
Any help in how to achieve this would be great! I do write simple applications in C# but never had to work with datagrids etc. :(
Thanks!
The DataGridView control has a feature to automatically generate columns that can be set by the AutoGenerateColumns property. This property defaults to true - that is columns are by default auto generated.
However, columns are only automatically generated for public properties of the object you bind to the grid - fields do not show up.
Auto generation also works for check box columns when there is a public boolean property on the bound object.
So the simplest way to achieve your first two requirements is to change your FileObject class to this:
public class FileObject
{
public string FilePath { get; set; }
public string FileState { get; set; }
public bool Selected { get; set; }
}
If you cannot modify that class then next best would be the create a wrapper object that holds a file object:
public class FileObjectWrapper
{
private FileObject fileObject_;
FileObjectWrapper()
{
fileObject_ = new FileObject();
}
FileObjectWrapper(FileObject fo)
{
fileObject_ = fo;
}
public string FilePath
{
get { return fileObject_.filePath; }
set { fileObject_.filePath = value; }
}
public string FileState
{
get { return fileObject_.fileState; }
set { fileObject_.fileState= value; }
}
public bool Selected { get; set; }
}
You can then create your list to bind to (a BindingList is usually best) doing something like:
var fowList = new BindingList<FileObjectWrapper>();
foreach (FileObject fo in // here you have your list of file objects! )
{
fowList.Add(new FileObjectWrapper(fo));
}
dataGridView1.DataSource = fowList;
There are many ways to do the above but that is a general idea.
You can also add an unbound DataGridViewCheckBoxColumn to the grid, though I find it easier to have in the the bound list. Here is how if you do need to:
DataGridViewCheckBoxColumn c = new DataGridViewCheckBoxColumn();
c.Name = "Selected";
dataGridView1.Columns.Add(c);
Finally, for having a "SelectedAll" option in the header you will need to use custom code.
The article on CodeProject that Umesh linked to (CheckBox Header Column for DataGridView) looks quite easy to implement - they create a custom DataGridViewHeaderCell overriding the Paint and OnMouseClick methods.
Please refer the the below example, showing exactly what you are looking for
http://www.codeproject.com/Articles/20165/CheckBox-Header-Column-For-DataGridView
MonotouchDialog makes it very easy to create UITableView Dialogs, but sometimes questions like that one popup:
MonoTouch Dialog. Buttons with the Elements API
Now, I have a similar problem but quite different:
List<User> users = GetUsers();
var root =
new RootElement ("LoginScreen"){
new Section ("Enter your credentials") {
foreach(var user in users)
new StyledStringElement (user.Name, ()=> {
// tap on an element (but which one exactly?)
}
),
}
navigation.PushViewController (new MainController (root), true);
Now, the second parameter of StyledStringElement's constructor has the type of NSAction delegate, and doesn't take any arguments, now I dunno how to determine exactly which element been tapped.
How to get that?
If it was Tapped then it has been selected. So you should be able to inherit from StyleStringElement and override its Selected method to accomplish the same goal.
e.g.
class UserElement : StyleStingElement {
public UserElement (User user) { ... }
public override Selected (...)
{
// do your processing on 'user'
base.Selected (dvc, tableView, indexPath);
}
}
For Touch.Unit I created a new *Element for every item I had, TestSuiteElement, TestCaseElement, TestResultElement... to be able to customize each of them and adapt (a bit) their behaviour but I did not use this Selected to replace Tapped. You might want to check but it would not fit with your code pattern to create elements.
"...a Flower by any other name?"
If you look closely NSAction's are just delegates. I prefer to pass Action / Func into those params the reference for which is contained within the...container controller.
So lets so you have a UINavigationController that pushes a DialogViewController. When your element is selected you provide the unique user that you've passed to the Element and go from there :-)
public class MyNavController : UINavigationController
{
Action<User> UserClickedAction;
public MyNavController()
{
UserClickedAction = HandleUserClicked;
}
public void HandleUserClicked(User user)
{
...
}
}