Im trying to locate an SPDataSource control located on my SharePoint page. I found the following code which probably works fine, I just don't know what to pass into it.
public static Control FindControlRecursive(Control Root, string Id)
{
if (Root.ID == Id)
return Root;
foreach (Control Ctl in Root.Controls)
{
Control FoundCtl = FindControlRecursive(Ctl, Id);
if (FoundCtl != null)
return FoundCtl;
}
return null;
}
I don't know how to have it search the whole page or at the very least the ContentPlaceHolder that the control is in.
edit
Looks like I have a more rudimentary issue here. Not sure how to explain but I'm not opening up the page before running my code. I'm opening the site via the following:
using (SPWeb web = thisSite.Site.OpenWeb("/siteurl/,true))
So when I try to find the page below I'm getting Object reference not set to instance of object.
var page = HttpContext.Current.Handler as Page;
Perhaps I'm going about this the wrong way, I'm in my infancy here so I'm just kind of stumbling along figuring stuff out!
What you got is actually not SharePoint specific, it's c# asp.net.
Anyway, you could call it like this
var page = HttpContext.Current.Handler as Page;
var control = page; // or put the element you know exist that omit (is a parent) of the element you want to find
var myElement = FindControlRecursive(control, "yourelement");
Most likely you'll need to cast the return as well
var myElement = (TextBox)FindControlRecursive(control, "yourelement");
// or
var myElement = FindControlRecursive(control, "yourelement") as TextBox;
There are however more efficient ways to write such a method, here is one simple example
public static Control FindControlRecursive(string id)
{
var page = HttpContext.Current.Handler as Page;
return FindControlRecursive(page, id);
}
public static Control FindControlRecursive(Control root, string id)
{
return root.ID == id ? root : (from Control c in root.Controls select FindControlRecursive(c, id)).FirstOrDefault(t => t != null);
}
Call it the same way as I suggested earlier.
If you are handling larger pages the methods above might be a bit slow, what you should do is aim for a method using generics instead. They are way faster than traditional methods.
Try this one
public static T FindControlRecursive<T>(Control control, string controlID) where T : Control
{
// Find the control.
if (control != null)
{
Control foundControl = control.FindControl(controlID);
if (foundControl != null)
{
// Return the Control
return foundControl as T;
}
// Continue the search
foreach (Control c in control.Controls)
{
foundControl = FindControlRecursive<T>(c, controlID);
if (foundControl != null)
{
// Return the Control
return foundControl as T;
}
}
}
return null;
}
You call it like this
var mytextBox = FindControlRecursive<TextBox>(Page, "mytextBox");
Related
So I have a user control with six textboxes and a few buttons. One of those buttons is 'clear'. When I click the clear button, in the btnClear_Click handler, I want to find all the textboxes in my user control (and ONLY in my user control). And then set them to an empty string. That's it. That's all.
This is turning out to be a herculean, insurmountably difficult thing to do. Finding an answer is like trying to map the human genome. I just want to ITERATE THROUGH THE CONTROLS. Nothing more.
I'm not interested in hearing about the merits of what I'm trying to do. Just the mechanics of how to do it. Something like this:
public partial class myUserControl: UserControl
{
private void btnClear_Click(object sender, RoutedEventArgs e)
{
var allMyControls = SomeMiraculousOperationToGetAllControlsThatOnlyExistInMyUserControl();
foreach (var control in allMyControls)
{
if (control is TextBox)
((TextBox)control).Text = string.Empty;
}
}
}
You can use the VisualTreeHelper to enumerate the child controls of your user control.
You can find an extension method base on this class here
public static T GetChildOfType<T>(this DependencyObject depObj)
where T : DependencyObject
{
if (depObj == null) return null;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
var child = VisualTreeHelper.GetChild(depObj, i);
var result = (child as T) ?? GetChildOfType<T>(child);
if (result != null) return result;
}
return null;
}
I'm creating my own IntelliSense Presenter, since Visual Studio2012 support change theme, so I want my background color of the presenter can be auto-changed when the theme been changed. Is there a way to track the theme changes event, or get the current color theme of the Visual Studio?
Yes, this is possible. I had to solve a similiar issue with one of my extensions...
The current theme is stored in the Windows Registry; so I implemented the following utility class.
public enum VsTheme
{
Unknown = 0,
Light,
Dark,
Blue
}
public class ThemeUtil
{
private static readonly IDictionary<string, VsTheme> Themes = new Dictionary<string, VsTheme>()
{
{ "de3dbbcd-f642-433c-8353-8f1df4370aba", VsTheme.Light },
{ "1ded0138-47ce-435e-84ef-9ec1f439b749", VsTheme.Dark },
{ "a4d6a176-b948-4b29-8c66-53c97a1ed7d0", VsTheme.Blue }
};
public static VsTheme GetCurrentTheme()
{
string themeId = GetThemeId();
if (string.IsNullOrWhiteSpace(themeId) == false)
{
VsTheme theme;
if (Themes.TryGetValue(themeId, out theme))
{
return theme;
}
}
return VsTheme.Unknown;
}
public static string GetThemeId()
{
const string CategoryName = "General";
const string ThemePropertyName = "CurrentTheme";
string keyName = string.Format(#"Software\Microsoft\VisualStudio\11.0\{0}", CategoryName);
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName))
{
if (key != null)
{
return (string)key.GetValue(ThemePropertyName, string.Empty);
}
}
return null;
}
}
Okay; this just helps to figur out the current settings... listening for the theme changed notification is a bit trickier. After your package is loaded, you must obtain an IVsShell instance via the DTE; once you have this object you can utilize the AdviceBroadcastMessages method to subscribe for event notifications. You have to provide an object whose type implements the IVsBroadcastMessageEvents interface...
I don´t want to post the whole implementation, but the following lines might illustrate the key scenario...
class VsBroadcastMessageEvents : IVsBroadcastMessageEvent
{
int IVsBroadcastMessageEvent.OnBroadcastMessage(uint msg, IntPtr wParam, IntPtr lParam)
{
const uint WM_SYSCOLORCHANGE = 0x15;
if (msg == WM_SYSCOLORCHANGE)
{
// obtain current theme from the Registry and update any UI...
}
}
}
Consider implementing IDisposable on that type as well, in order to be able to unsubscribe from the event source, when the package gets unloaded.
This is how I subscribe for event notifications...
class ShellService
{
private readonly IVsShell shell;
private bool advised;
public ShellService(IVsShell shellInstance)
{
this.shell = shellInstance;
}
public void AdviseBroadcastMessages(IVsBroadcastMessageEvents broadcastMessageEvents, out uint cookie)
{
cookie = 0;
try
{
int r = this.shell.AdviseBroadcastMessages(broadcastMessageEvents, out cookie);
this.advised = (r == VSConstants.S_OK);
}
catch (COMException) { }
catch (InvalidComObjectException) { }
}
public void UnadviseBroadcastMessages(uint cookie)
{
...
}
}
Keep the value of the cookie parameter; you´ll need it to successfully unsubscribe.
Hope that helps (-:
Just wanted to put an update just in case anyone else comes along.. #Matze and #Frank are totally right.. However in VS 2015.. they added a easy way to detect the theme change. So you need to include PlatformUI an dyou get a super easy event
using Microsoft.VisualStudio.PlatformUI;
....
//Then you get an event
VSColorTheme.ThemeChanged += VSColorTheme_ThemeChanged;
You should make sure your control is disposable so you can unsubscribe from the event...
BONUS!
It also give you easy access to the colors.. even if the user has changed them from the default .. so you can do stuff like this in when set your colors
var defaultBackground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowBackgroundColorKey);
var defaultForeground = VSColorTheme.GetThemedColor(EnvironmentColors.ToolWindowTextColorKey);
For VS 2015 this has changed, the solution #Matze has still works but you need to update the GetThemeId() function to check for the version and if it's 14.0 (VS2015) look in a different place in the registry. The way the value is stored has changed also, it's still a string but now contains other values seperated by a '*'. The theme guid is the last value in the list.
if (version == "14.0")
{
string keyName = string.Format(#"Software\Microsoft\VisualStudio\{0}\ApplicationPrivateSettings\Microsoft\VisualStudio", version);
using (RegistryKey key = Registry.CurrentUser.OpenSubKey(keyName))
{
if (key != null)
{
var keyText = (string)key.GetValue("ColorTheme", string.Empty);
if (!string.IsNullOrEmpty(keyText))
{
var keyTextValues = keyText.Split('*');
if (keyTextValues.Length > 2)
{
return keyTextValues[2];
}
}
}
}
return null;
}
I recently watched the very useful orchard harvest video on localization and internationalization, by Piotr Szmyd
I want to set the culture using this method, checking for a cookie
public class CultureSelector : ICultureSelector
{
public const int SelectorPriority = 5;
public const string CookieName = "Riders-Location-Cookie";
public const string CookieValueName = "location-code";
public CultureSelectorResult GetCulture(HttpContextBase context)
{
if (context == null || context.Request == null || context.Request.Cookies == null)
{
return null;
}
// check for a cookie
var cookie = context.Request.Cookies[CookieName];
if (cookie != null && !string.IsNullOrEmpty(cookie.Values[CookieValueName]))
{
return new CultureSelectorResult { Priority = SelectorPriority, CultureName = cookie.Values[CookieValueName] };
}
return null;
}
}
That works, However, I do want to user to be able to reset their own culture on the site. How do I reset the culture for the entire site when the user chooses to.
Lets say for instance if I have a select list that is output as part of a custom module.
I've looked at the ChangeCulture code form the Orchard CulturePicker Module but this dosen't seem to change to culture for the entier site as setting it with an implementation of ICultureSelector would.
If I correctly understand, you'd like a user to be able to change his current culture and/or be able to return to the default site culture, right?
In your case it should be as easy as either changing the cookie value or removing it (to set default culture) as a response to some user action.
I saw 2 different way to create web parts for sharepoint. Which one is preferred by most?
http://msdn.microsoft.com/en-us/library/aa973249%28office.12%29.aspx
Anything involving VSeWSS is just going to end in pain, so method 1 is definitely out. Method 2 isn't ideal either, as setting up html elements as controls becomes unmanageable at a level just beyond what you see in that demo. I use a fairly simple generic base class that takes a user control as a type parameter and lets me keep all the layout nicely seperated from the sharepoint infrastructure. If you are creating pages/web parts programatically most of the web part xml turns out to be optional also.
public abstract class UserControlWebPart<T> : Microsoft.SharePoint.WebPartPages.WebPart where T:UserControl
{
protected UserControlWebPart()
{
this.ExportMode = WebPartExportMode.All;
}
protected virtual void TransferProperties(T ctrl)
{
var tc = typeof(T);
var tt = this.GetType();
foreach (var p in tt.GetProperties()) {
if (p.IsDefined(typeof(ControlPropertyAttribute), true)) {
foreach (var p2 in tc.GetProperties()) {
if (p2.Name == p.Name) {
p2.SetValue(ctrl, p.GetValue(this, null), null);
}
}
}
}
}
protected override void CreateChildControls()
{
string controlURL = ControlFolder+typeof(T).Name+".ascx";
var ctrl = Page.LoadControl(controlURL) as T;
TransferProperties(ctrl);
this.Controls.Add(ctrl);
}
protected virtual string ControlFolder
{
get {
return "~/_layouts/UserControlWebParts/";
}
}
}
For the few web parts I've written, I guess I've gone more with method #2 than method #1. Seems more straightforward and has the potential to be reused outside of the SharePoint environment (depending on the depth of your business logic).
Can anyone see why this should not work:
SPSite topNavigationSite = new SPSite("http://moss");
SPWeb topNavigationWeb = topNavigationSite.OpenWeb();
SPNavigationNodeCollection topNavigationBarNodes = topNavigationWeb.Navigation.TopNavigationBar;
SPNavigationNode updateNode = topNavigationBarNodes.Navigation.GetNodeByUrl("/about");
updateNode.Url = "";
topNavigationWeb.Update();
I can see debugging that the url get's set to "" but when the page renders, the navigation still shows the url as /about/default.aspx
I'm running this in page_load and expected it to update the moss database with the new url value.
I know this is 3years old! but as there is no where online about updating a current url!
I had to do some debugging and this is what iv come up with! By the way I got the hint from
topNavigationWeb.Update();
indicating that its updating a list! hint hint!
a bit of background! I wanted to update the quick links from a list when they add, update and delete an item from the list! On my list I have two columns Title and URL!
I then created a project in VS 2010, its an event receiver that is connected only to that list (done in the elements.xml file)
within the .cs file I added item added, item deleting(not deleted ;) ) and item updated
public override void ItemAdded(SPItemEventProperties properties)
public override void ItemUpdated(SPItemEventProperties properties)
public override void ItemDeleting(SPItemEventProperties properties)
now within each method you can simply call this method!
add/update
public static void AddQuickLaunchItem(string header, string url, SPWeb web)
{
SPNavigationNodeCollection quickLaunch = web.Navigation.QuickLaunch;
// try to get quick launch header
SPNavigationNode nodeHeader =
quickLaunch.Cast<SPNavigationNode>().Where(n => n.Title == header).FirstOrDefault();
//if header not found create it
if (nodeHeader == null)
{
nodeHeader = quickLaunch.AddAsFirst(new SPNavigationNode(header, url,true));
}
else
{
web.AllowUnsafeUpdates = true;
nodeHeader = quickLaunch.Cast<SPNavigationNode>().Where(n => n.Title == header).First() ;
nodeHeader.Url = url;
web.AllowUnsafeUpdates = false;
}
nodeHeader.Update();
web.Update();
}
the first part is checking if the node exists using the title (header)! I'm comparing between what headers there are and from the list item:
SPNavigationNode nodeHeader =
quickLaunch.Cast<SPNavigationNode>().Where(n => n.Title == header).FirstOrDefault();
this part is the comparison:
n.Title == header
I'm getting these values (header and url / spweb) like so:
public static void AddQuickLaunchItem(string header, string url, SPWeb web)
the method that is calling the above would look like this:
private void addloopweblinks(SPSite siteCollection, SPItemEventProperties properties)
{
// Enumerate through each site and apply branding.
foreach (SPWeb web in siteCollection.AllWebs)
{
AddQuickLaunchItem(properties.ListItem["Title"].ToString(), properties.ListItem["URL"].ToString(), web);
}
}
and the above method is called within the itemadded and itemupdated ;) passing the values like so:
public override void ItemAdded(SPItemEventProperties properties)
{
using (SPSite siteCollection = new SPSite(properties.WebUrl))
{
if (siteCollection != null)
{
addloopweblinks(siteCollection, properties);
}
}
}
similar things can be done for the delete ;)
SPNavigationNode nodeHeader =
quickLaunch.Cast().Where(n => n.Title == header).FirstOrDefault();
that will get the node!
nodeHeader.delete();
nodeHeader.update();
that will delete the item!
so in your case what you need, I don't know if you noticed it was this part:
web.AllowUnsafeUpdates = true;
nodeHeader = quickLaunch.Cast<SPNavigationNode>().Where(n => n.Title == header).First() ;
nodeHeader.Url = url;
web.AllowUnsafeUpdates = false;
nodeHeader.Update();
web.Update();
as you can see what i mean by the hint! nodeHeader.Update(); didnt update the url, when debugging it does change but when i go to the update part it doesnt work :( and it clicked in i need web.AllowUnsafeUpdates = true; loool only becuse I experienced a similar situation before!
for you it would be:
using(SPSite topNavigationSite = new SPSite("http://moss"))
{
using(SPWeb topNavigationWeb = topNavigationSite.OpenWeb())
{
web.AllowUnsafeUpdates = true;
SPNavigationNodeCollection topNavigationBarNodes =topNavigationWeb.Navigation.TopNavigationBar;
SPNavigationNode updateNode = topNavigationBarNodes.Navigation.GetNodeByUrl("/about");
updateNode.Url = "";
updateNode.Update();
web.Update();
web.AllowUnsafeUpdates = false;
}
}
If the user doesnt have the sufficent rights than you need to encapsulate the above with runwithelevatedprivalages :) hope this helps :)
Have you tried:
updateNode.Update();
topNavigationWeb.Update();
It doesn't look like you are updating the SPNavigationNode object. (Note: you may not need the second Update call.)