I have a Windows 8 store app based off of the grouped template project, with some renames etc. However, I'm having a hard time getting the ItemsSource databinding to work for both non-snapped and snapped visual states.
I have a property, that, when set, changes the ItemsSource property, but I can only get one of the controls to bind at a time (either the GridView for non-snapped, or the ListView for snapped).
When I use the following, only the non-snapped binding works and the snapped binding shows no items:
protected PickLeafModel ListViewModel
{
get
{
return (PickLeafModel)m_itemGridView.ItemsSource;
}
set
{
m_itemGridView.ItemsSource = value;
m_snappedListView.ItemsSource = value;
}
}
If I comment out one of the setters, the snapped view shows items but the non-snapped view shows nothing:
protected PickLeafModel ListViewModel
{
get
{
return (PickLeafModel)m_itemGridView.ItemsSource;
}
set
{
//m_itemGridView.ItemsSource = value;
m_snappedListView.ItemsSource = value;
}
}
It's as if I can bind my view model only to one property at a time. What am I doing wrong?
Since I am generating my data model on another thread (yes, using the thread pool), I cannot make it inherit from DependencyObject. If I do, I get a WrongThreadException.
So to make it work I have done the following:
public class PickLeafModel : IEnumerable
{
public PickLeafModel()
{
}
public IEnumerator GetEnumerator()
{
if (m_enumerator == null)
{
m_enumerator = new PickLeafModelViewDataEnumerator(m_data, m_parentLeaf);
}
return m_enumerator;
}
private SerializableLinkedList<PickLeaf> m_data =
new SerializableLinkedList<PickLeaf>();
}
and then my items look like this:
// Augments pick leafs by returning them wrapped with PickLeafViewData.
class PickLeafModelViewDataEnumerator : IEnumerator
{
public PickLeafModelViewDataEnumerator(
SerializableLinkedList<PickLeaf> data, PickLeaf parentLeaf)
{
m_viewDataList =
new System.Collections.Generic.LinkedList<PickLeafViewData>();
foreach (PickLeaf leaf in data)
{
PickLeafViewData viewData = new PickLeafViewData();
viewData.copyFromPickLeaf(leaf, parentLeaf);
m_viewDataList.AddLast(viewData);
}
m_enumerator = m_viewDataList.GetEnumerator();
}
public void Dispose()
{
m_viewDataList = null;
m_enumerator = null;
}
public object Current
{
get
{
return m_enumerator.Current;
}
}
public bool MoveNext()
{
return m_enumerator.MoveNext();
}
public void Reset()
{
m_enumerator.Reset();
}
private IEnumerator<PickLeafViewData> m_enumerator = null;
private System.Collections.Generic.LinkedList<PickLeafViewData>
m_viewDataList;
}
}
Is there something I'm doing fundamentally wrong?
Help appreciated.
Thanks!
Thankfully there is a much easier way to do what you are trying!
Create a class called your ViewModel as shown below:
public class DataViewModel
{
public DataViewModel()
{
Data = new ObservableCollection<PickLeafViewData>(new PickLeafModelViewDataEnumerator(m_data, m_parentLeaf));
}
public ObservableCollection<PickLeafViewData> Data
{
get;
set;
}
}
Now on the code behind set the Page.DataConected to equal an instance of the above class.
And finally on both your snapped listview, and the grid view set the item source to this:-
ItemsSource="{Binding Data}"
That should work nicely for you.
Thanks to Ross for pointing me in the right direction.
I'm not 100% happy with this solution, but it does work. Basically the idea is that after I get back the PickLeafModel from the worker threads, I transplant its internal data into a derived version of the class which is data binding aware.
public class PickLeafViewModel : PickLeafModel, IEnumerable
{
public PickLeafViewModel()
{
}
public PickLeafViewModel(PickLeafModel model)
{
SetData(model);
}
public void SetData(PickLeafModel model)
{
model.swap(this);
}
public IEnumerator GetEnumerator()
{
if (m_observableData == null)
{
m_observableData = new ObservableCollection<PickLeafViewData>();
var data = getData();
PickLeaf parentLeaf = getParentLeaf();
foreach (PickLeaf leaf in data)
{
PickLeafViewData viewData = new PickLeafViewData();
viewData.copyFromPickLeaf(leaf, parentLeaf);
m_observableData.Add(viewData);
}
}
return m_observableData.GetEnumerator();
}
and the page code is as follows:
protected PickLeafViewModel ListViewModel
{
get
{
return DataContext as PickLeafViewModel;
}
set
{
DataContext = value;
}
}
whenever I want to set ListViewModel, I can do this:
ListViewModel = new PickLeafViewModel(model);
and swap looks like:
private static void swap<T>(ref T lhs, ref T rhs)
{
T temp;
temp = lhs;
lhs = rhs;
rhs = temp;
}
// Swaps internals with the other model.
public void swap(PickLeafModel other)
{
swap(ref m_data, ref other.m_data);
...
Also, PickLeafModelViewDataEnumerator can be deleted altogether.
Related
I have the following ViewModel and I am using Caliburn Micro. The IWindowManager instance is properly resolved and all of the code works. As indicated by the TODO comment, I need to get a reference to the current window so I can toggle the AlwaysOnTop attribute. How can I do that?
namespace CaliburnWizardPlay
{
[Export(typeof(DropWindowViewModel))]
public class DropWindowViewModel : PropertyChangedBase, IHaveDisplayName
{
private readonly IWindowManager windowManager;
[ImportingConstructor]
public DropWindowViewModel(IWindowManager windowManager)
{
this.windowManager = windowManager;
}
public string DisplayName
{
get { return "Main Window"; }
set { }
}
public bool AlwaysOnTop
{
get { return Settings.Default.DropWindowAlwaysOnTop; }
set
{
Settings.Default.DropWindowAlwaysOnTop = value;
Settings.Default.Save();
NotifyOfPropertyChange(() => AlwaysOnTop);
//todo: toggle the AOT attribute of the window
}
}
public void FileDropped(DragEventArgs eventArgs)
{
if (eventArgs.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] droppedFilePaths = eventArgs.Data.GetData(DataFormats.FileDrop, true) as string[];
foreach (string path in droppedFilePaths)
{
MessageBox.Show(path);
}
windowManager.ShowWindow(new WizardViewModel());
}
}
}
}
You can use the settings parameter of the ShowWindow method to set any property (e.g. Topmost) on the created window with a dictionary containing propertyname-value pairs:
windowManager.ShowWindow(new WizardViewModel(),
settings: new Dictionary<string,object> { {"Topmost", AlwaysOnTop} });
If you want to change the Topmost property of the already created window I see three options (in the order of preference):
Create an AlwaysOnTop property on the WizardViewModel and store the viewmodel in a private field and delegate the AlwaysOnTop to the WizardViewModel:
private WizardViewModel wizardViewModel;
public void FileDropped(DragEventArgs eventArgs)
{
//...
wizardViewModel = new WizardViewModel()
windowManager.ShowWindow(wizardViewModel);
}
public bool AlwaysOnTop
{
get { return Settings.Default.DropWindowAlwaysOnTop; }
set
{
//...
if (wizardViewModel != null)
wizardViewModel.AlwaysOnTop = value;
}
}
And in your view you can bind the WizardViewModel's AlwaysOnTop property to the window's TopMost property.
You can use the Application.Windows to retrieve the window. E.g. set the Name property of the created Window with the settings dictionary and then:
windowManager.ShowWindow(new WizardViewModel(),
settings: new Dictionary<string,object>
{ {"Topmost", AlwaysOnTop}, {"Name", "WizardWindow"} });
public bool AlwaysOnTop
{
get { return Settings.Default.DropWindowAlwaysOnTop; }
set
{
//...
var wizardViewModel = Application.Current.Windows.OfType<Window>()
.SingleOrDefault(w => w.Name == "WizardWindow");
if (wizardViewModel != null)
wizardViewModel.AlwaysOnTop = value;
}
}
Derive from the WindowManager and register it in your Bootstrapper and then you can override the CreateWindow, EnsureWindow etc. methods to store the created windows somewhere set the additional properties etc.
public partial class ChatUserControl : System.Web.UI.UserControl
{
UserChatClass ucc = new UserChatClass();
public ChatUserControl()
{
lblChatFriend = new Label();
txtChatMessage = new TextBox();
imgFriend = new Image();
rpChatMessages = new Repeater();
}
public string ChatFriend { get { return this.lblChatFriend.Text; } set { this.lblChatFriend.Text = value; } }
public string imgFriendUrl { get { return this.imgFriend.ImageUrl; } set { this.imgFriend.ImageUrl = value; } }
public object rpChatDataSource { get { return this.rpChatMessages.DataSource; } set { this.rpChatMessages.DataSource = value; } }
public Repeater rpChatMessagesToBind { get { return this.rpChatMessages; } set { this.rpChatMessages = value; } }
}
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
ChatUserControl user1 = new ChatUserControl();
divChatUserControlCover.Controls.Add(user1);
}
}
private void BindUserControls()
{ ChatUserControl user1 = divChatUserControlCover.Controls[1] as ChatUserControl;
user1.ChatFriend = row["username"].ToString();
user1.imgFriendUrl = "../../HttpImageHandler.jpg?username=" + row["username"].ToString();
DataSet dsCM = ucc.GetChatMessages(Session["username"].ToString(), row["username"].ToString());
user1.rpChatDataSource = dsCM;
user1.DataBindForRpChatMessagesToBind();
user1.Visible = true;
}
Master.aspx
<div id="divChatUserControlCover" runat="server">
</div>
Ok I have edited the code and now I have created properties. How do I call the DataBind method for rpChatMessages? I also cant see my usercontrol on page. Why
I'm not sure if your trying to reference the first label or second label. If its the second lable you can't just do chatMessage. you would have to do
((Label)rpChatMessages.FindControl("chatMessage")) due to scope of controls.
When you reference a component inside another component (ie Repeater) the child component no longer belongs to the document (implied this) but rather belongs to the control, ie
this.rpChatMessages { chatMessage }
I think you are just trying to pass a value to one control inside a UserControl if this is correct, declare a public property like this:
ASCX code behind
public string MyProperty
{
get
{
return this.lbl.Text;
}
set
{
this.lbl.Text = value;
}
}
Setting the value to the UserControl
private void BindUserControls()
{
ChatUserControl user1 = divChatUserControlCover.Controls[1] as ChatUserControl;
user1.MyProperty = row["username"].ToString();
Setting the value in the page markup
<uc1:ChatUserControl MyProperty='<%# Eval("some field") %>' ...
Edit 1
Remove that line
public object rpChatDataSource { get { return this.rpChatMessages.DataSource; } set { this.rpChatMessages.DataSource = value; }
And instead add a method
public void BindMyRepeaterOrWhatever(IEnumerable<Yourentity> data)
{
this.myDataBoundControl.DataSource = data;
this.myDataBoundControl.DataBind();
}
You can change the IEnumerable<Yourentity> data for object data but if you can pass a strongly typed enumeration would be better
To my surprise I found why my user control's child controls dont get instantiated. Its because ChatUserControl user1 = new ChatUserControl() doesnt get its child controls initialized.
The proper way to create a new intance of user control is this way....
ChatUserControl user1 = (ChatUserControl)Page.LoadControl("~/ChatUserControl.ascx");
I have a custom asp-net control that inherits from another one and its works as expected, though the properties are only set properly if i code them in the markup directly, so for instance if i need set a property at runtime that is some dynamic value, this value is never set or somehow lost.
Here's the markup code:
<!--related form-->
<fw:advancedformdisplay id="formDisp" runat="server" captchaenabled="true" EmailEnabled="true" EnableViewState="true" captchaprivatekey="xxxxxxxxxxxxxxxxxxxx" captchapublickey="xxxxxxxxxxxxx" captchatheme="white" SourceType="MenuItem" SourceMainId="Auto">
</fw:advancedformdisplay>
This is the code of the control:
[DefaultProperty("CaptchaEnabled"),ToolboxData("<{0}:AdvancedFormDisplay runat=server></{0}:AdvancedFormDisplay>"), Description("This is an enhanced FormDisplay control that inlcudes Googles Captcha control is enabled")]
public class AdvancedFormDisplay :SiteBuilder.WebControls.FormDisplay
{
bool _CaptchaEnabled = false, sendEmail = false;
string captchaErrorMessage = "The verification code entered is not valid. Please try again!";
RecaptchaControl captchaControl = null;
string captchaPrivateKey = "", captchaPublicKey = "", captchaTheme = "clean";
string originalFormHtml = string.Empty;
string afterText = string.Empty, beforeText = string.Empty;
Literal litHtmlForm = null;
string captchaErrorClass = "errorCaptcha";
public string EmailBeforeText
{
get { return beforeText; }
set { beforeText = value; }
}
public string EmailAfterText
{
get { return afterText; }
set { afterText = value; }
}
public string CaptchaErrorClass
{
get { return captchaErrorClass; }
set { captchaErrorClass = value; }
}
public bool CaptchaEnabled
{
get { return _CaptchaEnabled; }
set { _CaptchaEnabled = value; }
}
public bool EmailEnabled
{
get { return sendEmail; }
set { sendEmail = value; }
}
public string CaptchaErrorMessage
{
get { return captchaErrorMessage; }
set { captchaErrorMessage = value; }
}
/// <summary>
/// red,white,blackglass,clean
/// </summary>
public string CaptchaTheme
{
get { return captchaTheme; }
set { captchaTheme = value; }
}
public string CaptchaPrivateKey
{
get { return captchaPrivateKey; }
set { captchaPrivateKey = value; }
}
public string CaptchaPublicKey
{
get { return captchaPublicKey; }
set { captchaPublicKey = value; }
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
}
public override void OnSaved(FormDisplayEventArgs e)
{
//If captcha control is enabled we need to adda bit of code to redirect form properly
if (CaptchaEnabled && e.Redirect && !e.SendMail)
{
//Do Stuff
}
if(sendEmail)
{
//Send email
}
base.OnSaved(e);
}
public override void OnSaving(FormDisplayEventArgs e)
{
if (CaptchaEnabled)
{
//Validate and do stuff
}
base.OnSaving(e);
}
}
And then in my asp.net page that is using control, created by markup code, in the Page_Load() i try to assign some values to some properties and and the values aren't set properly, meaning that if i have set for isntance, the property EmailBeforeText = "somthing" this value will not be assigned..
protected void Page_Load(object sender, EventArgs e)
{
//2: Get the language of menuitem - Based on current culture setting (for by dropdownbox - change logic)
try
{
currentCulture = Thread.CurrentThread.CurrentCulture.ToString();
// Redirect if domain does not match rootnode.
DomainChecker.CheckURL(this.Request, this.Response, currentCulture);
if (footerArticle != null)
footerArticle.SourceMenuId = Digimaker.Config.Custom.Get("FooterID_" + currentCulture).ToString();
}
catch
{
currentCulture = "en-GB";
if( footerArticle != null )
footerArticle.SourceMenuId = Digimaker.Config.Custom.Get("FooterID_" + currentCulture).ToString();
}
Any ideas what i'm missing here?
Thanks a lot for your reading!
Regards,
byte_slave
short answer: use viewstate to persist your custom values!
Understanding ASP.NET ViewState whitepaper (see example with NavigateUrl)
edit: as reading the white-paper is obviously a really hard thing:
Each control is responsible for storing its own state, which is
accomplished by adding its changed state to its ViewState property.
The ViewState property is defined in the System.Web.UI.Control class,
meaning that all ASP.NET server controls have this property available.
(When talking about view state in general I'll use lower case letters
with a space between view and state; when discussing the ViewState
property, I'll use the correct casing and code-formatted text.)
If you examine the simple properties of any ASP.NET server control
you'll see that the properties read and write directly to the view
state. (You can view the decompiled source code for a .NET assembly by
using a tool like Reflector.) For example, consider the HyperLink Web
control's NavigateUrl property. The code for this property looks like
so:
public string NavigateUrl
{
get
{
string text = (string) ViewState["NavigateUrl"];
if (text != null)
return text;
else
return string.Empty;
}
set
{
ViewState["NavigateUrl"] = value;
}
}
As this code sample illustrates, whenever a control's property is
read, the control's ViewState is consulted. If there is not an entry
in the ViewState, then the default value for the property is returned.
When the property is assigned, the assigned value is written directly
to the ViewState.
I have 45 dropdown lists in my asp page. There are some methods that I can apply to all of these dropdowns. Is it possible to convert them into an array of dropdowns for ease of use?
I would use recursion to look for all dropdowns on your page. Based on this post it would be something like:
public static List<T> FindControls<T>(System.Web.UI.ControlCollection Controls) where T : class
{
List<T> found = new List<T>();
FindControls<T>(Controls, found);
return found;
}
private static void FindControls<T>(System.Web.UI.ControlCollection Controls, List<T> found) where T : class
{
if (Controls != null && Controls.Count > 0)
{
for (int i = 0; i < Controls.Count; i++)
{
if (Controls[i] is T)
{
found.add(Controls[i] as T);
}
else
FindControl<T>(Controls[i].Controls, found);
}
}
}
Once you have your list of dropdowns you can apply whatever methods you see fit.
Using Linq to Objects, an Extension method and Generics we can make this very concise thus:
Method Call to Get all DropDowns
var DropDowns = FindAllControlsByType<DropDownList>(MyBaseControlArray);
Find Controls Method
public static IEnumerable<Control> FindAllControlsByType<T>(IEnumerable<Control> MyCollection) where T : class
{
return MyCollection.Cast<Control>().Descendants(d => d.Controls.Cast<Control>()).Where(l => l.GetType().Equals(typeof(T)));
}
Descendants Extension Method
static public class LinqExtensions
{
static public IEnumerable<T> Descendants<T>(this IEnumerable<T> source,
Func<T, IEnumerable<T>> DescendBy)
{
foreach (T value in source)
{
yield return value;
foreach (T child in DescendBy(value).Descendants<T>(DescendBy))
{
yield return child;
}
}
}
}
EDIT
I've been looking at making this all a mite more generic so here is a completely generic solution that will traverse an object graph from a specified start point extracting all elements of the given type.
public static class Utils
{
public static IEnumerable<IEnumerable<T>> GetCollections<T>(object obj)
{
if (obj == null) throw new ArgumentNullException("obj");
var type = obj.GetType();
var res = new List<IEnumerable<T>>();
foreach (var prop in type.GetProperties())
{
// is IEnumerable<T>?
if (typeof(IEnumerable<T>).IsAssignableFrom(prop.PropertyType))
{
var get = prop.GetGetMethod();
if (!get.IsStatic && get.GetParameters().Length == 0) // skip indexed & static
{
var collection = (IEnumerable<T>)get.Invoke(obj, null);
if (collection != null) res.Add(collection);
}
}
}
return res;
}
public static IEnumerable<V> FindAllControlsByType<V, T>(V MyCollection) where T : class
{
return Utils.GetCollections<V>(MyCollection).Descendants(d => d).Where(l => typeof(T).IsAssignableFrom(l.GetType()));
}
}
static public class LinqExtensions
{
static public IEnumerable<T> Descendants<T>(this IEnumerable<IEnumerable<T>> source,
Func<IEnumerable<IEnumerable<T>>, IEnumerable<IEnumerable<T>>> DescendBy)
{
foreach (IEnumerable<T> collection in source)
{
foreach (T value in collection)
{
yield return value;
foreach (T child in DescendBy(Utils.GetCollections<T>(value)).Descendants<T>(DescendBy))
{
yield return child;
}
}
}
}
}
And we can call that using:
var DropDowns = Utils.FindAllControlsByType<Control, DropDownList>(BaseControl);
Basically the two types are the base class and the specific child class that you want to extract. You'll notice that the process identifies all collections of the base class that are contained within each instance of the base class. This means we're not tied to Controls as the collection and could use this within other structures. Any additional optimisations welcomed.
Greetings,
I have a ViewModel for a ProductCategory.
The ProductCategory has a boolean Active field.
Is is possible to have a single ProductCategoryViewModel and be able to get a collection of all ProductCategories and a collection of ACTIVE ProductCategories?
Or, would I have to create an ActiveProductCategoryViewModel?
I'm using MVVM-Light with RIA in Silverlight...so, I have a ProductCategory service with a GetProductCategories method and a GetActiveProductCategories method. I want to be able to get the ActiveProductCategories to populate a dropdown...but also get ALL the ProductCategories for maintenance and historical purposes etc.
Thanks!
Butcher
I assume you have another ViewModel with a collection of ProductCategoryViewModel objects? If so, I think its fine to have another collection of just the active product categories. I'm not sure you need a separate service method for this, since you can just filter your collection of product categories based on the Active value.
If this view model would be called ProductCategoriesViewModel, it might look like this:
using System.Collections.Generic;
using System.Linq;
using GalaSoft.MvvmLight;
namespace OCEAN.EPP.ViewModel
{
public class ProductCategoriesViewModel : ViewModelBase
{
public ProductCategoriesViewModel()
{
if (IsInDesignMode)
{
ProductCategories = new List<ProductCategoryViewModel>
{
new ProductCategoryViewModel { Active = false },
new ProductCategoryViewModel { Active = false },
new ProductCategoryViewModel { Active = true },
new ProductCategoryViewModel { Active = true },
};
}
else
{
// Code runs "for real": Connect to service, etc...
}
}
public const string ProductCategoriesPropertyName = "ProductCategories";
private List<ProductCategoryViewModel> _productCategories = new List<ProductCategoryViewModel>();
public List<ProductCategoryViewModel> ProductCategories
{
get { return _productCategories; }
set
{
if (_productCategories == value)
return;
_productCategories = value;
FilterActiveProductCategories();
RaisePropertyChanged(ProductCategoriesPropertyName);
}
}
public const string ActiveProductCategoriesPropertyName = "ActiveProductCategories";
private List<ProductCategoryViewModel> _activeProductCategories = new List<ProductCategoryViewModel>();
public List<ProductCategoryViewModel> ActiveProductCategories
{
get { return _activeProductCategories; }
set
{
if (_activeProductCategories == value)
return;
_activeProductCategories = value;
RaisePropertyChanged(ActiveProductCategoriesPropertyName);
}
}
private void FilterActiveProductCategories()
{
ActiveProductCategories = ProductCategories.Where(pc => pc.Active).ToList();
}
}
}